java - Javascript <-> Java AES

标签 java javascript aes

我试图编写一个Web应用程序,该应用程序通过AJAX使用AES加密来与Java后端进行交互。

我花了一些时间寻找和测试库,但都没有一个能证明卓有成效。

我的Java <-> PHP可与以下Java代码一起正常工作:

public static String encrypt(String input, String key){
    IvParameterSpec ips = new IvParameterSpec("sixteenbyteslong".getBytes());
    try {
        key = md5(key);
    } catch (NoSuchAlgorithmException e1) {
        e1.printStackTrace();
    }
    byte[] crypted = null;
    try{
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skey, ips);
        crypted = cipher.doFinal(input.getBytes());
    }catch(Exception e){
        System.out.println(e.toString());
    }
    return new String(Base64.encodeBase64(crypted));
}

public static String decrypt(String input, String key){
    IvParameterSpec ips = new IvParameterSpec("sixteenbyteslong".getBytes());
    try {
        key = md5(key);
    } catch (NoSuchAlgorithmException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    byte[] output = null;
    try{
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, skey,ips);
        output = cipher.doFinal(Base64.decodeBase64(input));
    }catch(Exception e){
        System.out.println(e.toString());
    }
    return new String(output);
}


Base64是org.apache.commons.codec.binary.Base64

我尝试了SlowAES,但它不支持“ PKCS5Padding”,但是即使存在,实际的加密也可能无法正常工作。

最佳答案

我看过slowAes,我认为你是对的。坏了

该代码打算在CBC模式下运行时应用PKCS#7填充,但不会成功。 (PKCS#7只是扩展到16字节块加密算法的PKCS#5。我认为Java将PKCS#5与AES结合使用是一个错误-他们应该将其称为PKCS#7,因为他们正在做16字节填充)。

我修改了slowAes以正确进行填充,并进行了修改,使slowAes与Java代码之间具有良好的互操作性。但是您需要修改Java代码。稍后更多。

这是我使用的Java代码:(仅用于演示;不适用于实际应用)

private static MessageDigest md;
static {
    try {
        md = MessageDigest.getInstance("MD5");
    }
    catch(Exception e) {
        md = null;
    }
}

private static byte[] md5(String source) {
    byte[] bytes = source.getBytes();
    byte[] digest = md.digest(bytes);
    return digest;
}

public static String encrypt(String input, String key){
    byte[] ivbytes = "sixteenbyteslong".getBytes();  // <- NO NO NO NO  !!!!
    IvParameterSpec ips = new IvParameterSpec(ivbytes);
    System.out.println("plaintext: " + input);
    byte[] keybytes = md5(key);  // <- NO NO NO NO !!!!
    System.out.println("key      : " + Hex.encodeHexString(keybytes));
    System.out.println("iv       : " + Hex.encodeHexString(ivbytes));
    byte[] crypted = null;
    try{
        SecretKeySpec skey = new SecretKeySpec(keybytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skey, ips);
        byte[] ptext = input.getBytes();
        crypted = cipher.doFinal(ptext);
    }catch(Exception e){
        System.out.println(e.toString());
    }
    return new String(Hex.encodeHexString(crypted));
}

public static String decrypt(String input, String key){
    IvParameterSpec ips = new IvParameterSpec("sixteenbyteslong".getBytes());  // <- NO !!!
    byte[] keybytes = md5(key);  // <- BAD BAD BAD!!!
    System.out.println("key      : " + Hex.encodeHexString(keybytes));
    byte[] output = null;
    try{
        SecretKeySpec skey = new SecretKeySpec(keybytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, skey, ips);
        output = cipher.doFinal(Hex.decodeHex(input.toCharArray()));
    }catch(Exception e){
        System.out.println(e.toString());
    }
    return new String(output);
}

public void Run() {
    String plaintext = CommandLineArgs.get("pt");
    String keystring = CommandLineArgs.get("k");

    if (plaintext == null || keystring == null) {
        Usage();
        return;
    }

    System.out.println("encrypting...");
    String crypto = encrypt(plaintext, keystring);
    System.out.println("crypto   : " + crypto);
    System.out.println("decrypting...");
    String decrypted = decrypt(crypto, keystring);
    System.out.println("decrypted: " + decrypted);
}


(上面代码中的Hex类是org.apache.commons.codec.binary.Hex;它与您使用的Base64编码器位于同一jar中。我换了一下Base64,因为我想实际看到字节。)

这是输出:

encrypting...
plaintext: AbbaDabbaDo_Once_upon_a_time....
key      : 2e0160e078aa4b925e62b20610378253
iv       : 7369787465656e62797465736c6f6e67
crypto   : f353e4dd6fb11ea13254dfef670ad88f8fbebcd24217374c06daefbbfe152df504035ae2d82537392c9ab1f719993ec1
decrypting...
key      : 2e0160e078aa4b925e62b20610378253
decrypted: AbbaDabbaDo_Once_upon_a_time....


JS模块的输出:

key       : 2e0160e078aa4b925e62b20610378253
iv        : 7369787465656e62797465736c6f6e67
plaintext : AbbaDabbaDo_Once_upon_a_time....
ciphertext: f353e4dd6fb11ea13254dfef670ad88f8fbebcd24217374c06daefbbfe152df504035ae2d82537392c9ab1f719993ec1
decrypted : AbbaDabbaDo_Once_upon_a_time....


和JS代码:

var keystring = "keystring",
md5String = MD5.getDigest(keystring),
keybytes = cryptoHelpers.toNumbers(md5String), // <- NO NO NO!
iv = "sixteenbyteslong".getBytes(),  // <- NO NO NO
keysize, key = cryptoHelpers.toHex(keybytes),
plaintext, bytesToEncrypt, mode, result,
decrypted, recoveredText;
say("key       : " + key);
keysize = slowAES.aes.keySize.SIZE_128;
say("iv        : " + cryptoHelpers.toHex(iv));

plaintext = "AbbaDabbaDo_Once_upon_a_time....";

bytesToEncrypt = cryptoHelpers.convertStringToByteArray(plaintext);
mode = slowAES.modeOfOperation.CBC;
result = slowAES.encrypt(bytesToEncrypt,
                         mode,
                         keybytes,
                         keysize,
                         iv);

say( "plaintext : " + plaintext);
say( "ciphertext: " + cryptoHelpers.toHex(result.cipher));

decrypted = slowAES.decrypt(result.cipher,
                            result.mode,
                            keybytes,
                            keysize,
                            iv) ;

recoveredText = cryptoHelpers.convertByteArrayToString(decrypted);
say( "decrypted : " + recoveredText);


我对slowAes所做的修改是在encrypt()函数中。我添加了一个名为padLength的新变量,

    if (mode == this.modeOfOperation.CBC) {
        padLength = 16 - (bytesIn.length % 16);
    }
    // the AES input/output
    if (bytesIn !== null)
    {
        for (var j = 0;j < Math.ceil((bytesIn.length + padLength)/16); j++)
        {
        ....


您可以获取我使用的修改后的AES源heremy test program



重要提示:您不应使用密码短语的MD5来获取密钥字节。使用PBKDF2。有一个Java版本的PBKDF2,它可以工作,并且有a Javascript PBKDF2 that works。另外,某些J2EE服务器包含PBKDF2类。同样对于IV字节。这些也应来自密码短语。如果您对此表示怀疑,请使用read IETF RFC 2898作为依据。

不要将我上面发布的代码用于真实应用。对其进行修改以使用PBKDF2。



编辑
关于填充...


  解密返回的消息时,我将如何删除其他填充,因为我不一定知道未加密的长度。我以为填充字节本应等于填充长度,但似乎不相等。


AES是块加密器;它加密长度恰好为16个字节的块。如果输入32字节的纯文本,则将得到32字节的加密文本。如果输入1024字节,则输出1024字节。 (不完全正确,稍后您会看到原因。现在假设这是正确的。)

如您所见,当纯文本不是16字节的偶数倍时,由于AES需要16字节的块,所以出现了问题-我将“多余”的东西放在什么地方以构成完整的16字节的块?答案是填充。

有不同的是垫。在CBC模式下,典型的方式是PKCS#7(Java称它为PKCS#5,正如我之前所说,我认为这是用词不当)。如果发送25字节的明文,则填充表示AES实际上将加密32字节:25字节的实际数据和7字节的填充。好的,但是填充的7个字节中包含哪些值?

PKCS#7表示填充字节是值16-len,其中len是最后一块中实际数据字节的长度。换句话说,该值与您所说的填充字节数相同。在上面的示例中,如果加密25个字节,则需要7个填充字节,每个填充字节将取值为7。在加密之前,这些填充字节将添加到明文的末尾。它产生的加密流为16字节块的总数。

这很好,因为在解密时,解密器可以简单地查看解密流中的最后一个字节,并且现在知道要从该解密流中删除多少填充字节。使用PKCS#7填充,应用程序层无需担心在解密时删除填充或在加密时添加填充。 AES库应该处理所有这些。假设解密器解密32个字节的加密文本,并且所得明文中的最后一个字节的值为7。通过PKCS#7填充,解密器知道从最后一个块的末尾切出7个字节,并交付部分块对于应用程序,最后9个字节为最后一个字节,总共25个字节的纯文本。

Java可以正确做到这一点。 slowAES正确执行了此操作,但明文长度是16字节的倍数的情况除外。 PKCS#7表示,在这种情况下,您需要添加16个字节的填充(全部为16)。如果要精确加密32个字节,则AES的PKCS#7说,您需要添加16字节的pad。共加密48个字节。这样解密器才能做正确的事。考虑一下:如果不添加16个字节的填充,解密器将无法“告诉”明文的最后一个字节不是填充字节。

在这种情况下,SlowAES没有填充,这是您无法使其与Java互操作的部分原因。我注意到了这一点,因为对于32字节的明文,密码流恰好是32字节,这意味着没有填充。当我查看代码时,逻辑错误就在那里。 (提醒我:我需要验证在加密方面,在加密方面是否存在逻辑错误)

因此,您的应用程序不知道未加密的长度是正确的。但是,如果使用PKCS#7填充,则解密程序确实知道要分割多少字节,并且正确的解密程序将始终向您返回正确的明文字节数。为了使其正常工作,解密时必须使用与加密时相同的填充约定。您通常需要告诉加密库使用什么填充,尽管有些填充(例如slowAES)没有选择的余地。

如果在某些库中使用“ no padding”选项(但在CBC模式下不是在slowAES中使用),那么,是的,您的应用程序必须以某种方式“知道”未加密数据的大小,以便可以丢弃最后N个字节。纯文本。在某些数据格式和协议中可以。但是通常使用PKCS#7填充会更容易。



编辑

只是再看一遍,是的,slowAES中的解密逻辑也存在填充问题。它希望您传递“解密的长度”-我现在看到了,我看到这是您提出问题的原因。如果正确执行PKCS#7填充,则没有必要。不是现在应该是一个简单的解决方法。稍后将更新回这里。

编辑

好的,更新的AES文件现在可用here;更新的测试代码为here。它在加密和解密时正确执行PKCS#7填充。我可能应该将这些更改发送回slowAES的所有者。

编辑

哦,还有一件事:Performing encryption within Javascript is considered harmful

关于java - Javascript <-> Java AES,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7166275/

相关文章:

java - 如何打印 readline() 方法的结果

java - JedisPool 无法连接到 telnet redis 服务器

javascript - 将 ID 存储在 Meteor 中的 react 数组中

c++ - 使用 AES_256_CBC 解密文件返回 "bad decrypt"错误

linux - 使用 openssl 时如何隐藏 key 、salt 和 iv,使其不出现在进程标题中?

Java:将文档转换为现有文档:合并?

java - 使用温和的传输

javascript 将对象数组的数组求和在一起

javascript - 在不使用 `scrollHeight` 属性的情况下可靠地返回元素的 scrollHeight

java - (AES加密)代码缺陷,应该注意什么? [提供代码][Java]