Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/mercurial/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使用crypto js库在客户端加密消息并在Java服务器上解密_Java_Security_Encryption_Cryptography_Cryptojs - Fatal编程技术网

如何使用crypto js库在客户端加密消息并在Java服务器上解密

如何使用crypto js库在客户端加密消息并在Java服务器上解密,java,security,encryption,cryptography,cryptojs,Java,Security,Encryption,Cryptography,Cryptojs,背景: 我正在使用的应用程序应该脱机工作。我有一个HTML5页面,用户输入的数据使用crypto js库加密。 我希望将加密的消息发送到JavaWebServer,然后在服务器端对其进行解密 我在干什么 我能够使用Crypto js加密消息 <code> var message = "my message text"; var password = "user password"; var encrypted = CryptoJS.AES.encrypt( message ,pass

背景: 我正在使用的应用程序应该脱机工作。我有一个HTML5页面,用户输入的数据使用crypto js库加密。 我希望将加密的消息发送到JavaWebServer,然后在服务器端对其进行解密

我在干什么 我能够使用Crypto js加密消息

<code>
var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());
// this prints an encrypted text "D0GBMGzxKXU757RKI8hDuQ=="
</code>
我要做的是传递加密文本D0GBMGzxKXU757RKI8hDuQ== 发送到java服务器端代码,并解密所需的消息

我尝试了很多方法在java服务器端解密crypto js加密消息。 请在下面的服务器端找到我的代码,它应该对加密文本进行解密

<code>
public static String decrypt(String keyText,String encryptedText) 
{
// generate key 
Key key = new SecretKeySpec(keyText.getBytes(), "AES");
Cipher chiper = Cipher.getInstance("AES");
chiper.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
byte[] decValue = chiper.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}  
</code>
我从下面的代码中调用java方法decrypt

<code>
// performs decryption 
public static void main(String[] args) throws Exception 
{
String decryptedText = CrypterUtil.decrypt("user password","D0GBMGzxKXU757RKI8hDuQ==");
}
</code>
但是当我运行java解密代码时,我得到了以下异常

<code>
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(AESCipher.java:372)
at javax.crypto.Cipher.passCryptoPermCheck(Cipher.java:1052)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1010)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at au.gov.daff.pems.model.utils.CrypterUtil.decrypt(CrypterUtil.java:34)
at au.gov.daff.pems.model.utils.CrypterUtil.main(CrypterUtil.java:47)
Process exited with exit code 1.
</code>

我不确定我做错了什么?。。。使用crypto js库对消息进行加密的最佳方式是什么,以便在其他地方使用用户键入的密码对其进行说明。

您必须了解密码不是密钥。密码通常通过一些散列函数生成一个位字符串或字节数组,这是一个密钥。它无法打印,因此表示为十六进制或base64

在JavaScript中,您使用密码,但在Java中,您假定相同的密码是密钥,而事实并非如此。您可以确定CryptoJS如何散列密码以获得密钥并在Java中重新创建该密钥,但它的实现方式似乎是每次使用密码加密某个内容时都会生成一个新的salt,并且无法更改salt

如果你真的想从用户那里获得密码,那么你需要自己获得密钥。CryptoJS为此提供了PBKDF2,但它也需要额外的资源。您可以为应用程序生成一个并将其添加到代码中。您可以这样生成一次:

CryptoJS.lib.WordArray.random(128/8).toString();
要每次派生密钥,您需要将静态salt传递到AES-256的基于密码的密钥派生函数中

var key = CryptoJS.PBKDF2(userPassword,
        CryptoJS.enc.Hex.parse(salt),
        { keySize: 256/32, iterations: 1000 });
var iv = CryptoJS.lib.WordArray.random(256/8); // random IV
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
在服务器上,您需要执行以下操作。您还需要将服务器上的方案从AES调整为AES/CBC/PKCS5Padding,因为它是最新版本。注:对于AES,PKCS5和PKCS7是相同的

还要注意的是,您需要将IV从客户端传递到服务器,并将其初始化为

chiper.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));

当然,您可以使用PBKDF的Java实现在服务器上通过密码和salt重新创建密钥,或者只保存已知密码和salt的密钥。您可以使用用户可以接受的PBKDF迭代。AES和相关算法可以以多种不同的方式使用,在混合语言时,找出客户端使用的模式并将其与服务器的模式相匹配总是有点棘手

Java代码的第一个问题是不能将字符串的字节用作AES密钥。互联网上有很多这样做的例子,但这是大错特错的。就像CryptoJS代码中显示的@artjom-B一样,您需要使用基于密码的密钥派生函数,并且还需要在客户端和服务器上对其进行完全相同的参数化

此外,客户端需要生成salt并将其与加密文本一起发送;否则,服务器无法从给定密码生成相同的密钥。我不确定CryptoJS到底是如何做到这一点的。在Java中有一些合理的东西,您可以在了解CryptoJS的工作原理时调整参数:

public static SecretKey generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
    return new SecretKeySpec(keyBytes, "AES");
}
使用AES CBC,您还需要随机生成一个IV,并将其与加密文本一起发送

总而言之:

计算CryptoJS使用的AES参数。不确定它们是什么,但听起来像:密钥大小256,填充pkcs5,模式CBC,PBE算法PBKDF2,salt random,迭代计数100 使用相同的参数配置服务器 使用PBE密钥生成器以及非机密但随机的salt 将AES CBC与非机密但随机的IV一起使用 将密码文本、IV和盐发送到服务器 然后在服务器端,使用salt、迭代计数和密码生成AES密钥 然后base64对其进行解码和解密
感谢Artjom B和Isaac Potoczny Jones的及时回复和建议。为了他人的利益,我在下面给出了对我有效的完整解决方案

用于在Java服务器端解密cryptojs加密消息的Java代码

public static void main(String args[]) throws Exception{

    String password = "Secret Passphrase";
    String salt = "222f51f42e744981cf7ce4240eeffc3a";
    String iv = "2b69947b95f3a4bb422d1475b7dc90ea";
    String encrypted = "CQVXTPM2ecOuZk+9Oy7OyGJ1M6d9rW2D/00Bzn9lkkehNra65nRZUkiCgA3qlpzL";

    byte[] saltBytes = hexStringToByteArray(salt);
    byte[] ivBytes = hexStringToByteArray(iv);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);        
    SecretKeySpec sKey = (SecretKeySpec) generateKeyFromPassword(password, saltBytes);
    System.out.println( decrypt( encrypted , sKey ,ivParameterSpec));
}

public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {

    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);

    return new SecretKeySpec(secretKey.getEncoded(), "AES");
}

public static byte[] hexStringToByteArray(String s) {

    int len = s.length();
    byte[] data = new byte[len / 2];

    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                + Character.digit(s.charAt(i+1), 16));
    }

    return data;
}

public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception { 

    Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
    c.init(Cipher.DECRYPT_MODE, sKey, ivParameterSpec);
    byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
    byte[] decValue = c.doFinal(decordedValue);
    String decryptedValue = new String(decValue);

    return decryptedValue;
}

谢谢你的快速回复。我明白你的解释。但我只是想知道如何从服务器端用户提供的密码创建密钥。基本上,我希望在服务器端加密一些数据并将其传递给客户端。客户端用户应该能够输入他们的密码,然后解密消息…我应该怎么做?…我注意到,当我试图在客户端用相同的密码加密相同的文本时,密钥一直在变化。根据
如果密码没有更改,您所解释的密钥应该保持不变,这是正确的。由于某些原因,salt在从密码派生密钥时总是随机初始化。我已经编辑了我的答案。这里有一个关于如何使用CryptoJS加密/解密的全面指南,指定密钥大小、iv、模式、填充、PBE算法等-密码短语应该来自哪里?如果在js中,这不是秘密
function  generateKey(){
    var salt = CryptoJS.lib.WordArray.random(128/8);
    var iv = CryptoJS.lib.WordArray.random(128/8);
    console.log('salt  '+ salt );
    console.log('iv  '+ iv );
    var key128Bits100Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
    console.log( 'key128Bits100Iterations '+ key128Bits100Iterations);
    var encrypted = CryptoJS.AES.encrypt("Message", key128Bits100Iterations, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7  });
}

function  decrypt(){
    var salt = CryptoJS.enc.Hex.parse("4acfedc7dc72a9003a0dd721d7642bde");
    var iv = CryptoJS.enc.Hex.parse("69135769514102d0eded589ff874cacd");
    var encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
    var key = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
    console.log( 'key '+ key);
    var decrypt = CryptoJS.AES.decrypt(encrypted, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    var ddd = decrypt.toString(CryptoJS.enc.Utf8); 
    console.log('ddd '+ddd);
}