Javascript < - > Java AES

Mar*_*cus 2 javascript java aes

我正在尝试编写一个使用AES加密而不是AJAX的Web应用程序来与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);
}
Run Code Online (Sandbox Code Playgroud)

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

我尝试过SlowAES,但它不支持"PKCS5Padding",但即使存在这种情况,实际的加密也可能无法正常工作.

如果有人有任何Java的经验< - > Javascript AES我会非常感激,如果他们可以传授一些建议.

谢谢

Che*_*eso 5

我看着slowAes,我认为你是对的.它坏了.

该代码打算在CBC模式下运行时应用PKCS#7填充,但不成功.(PKCS#7只是PKCS#5扩展到一个16字节的块加密算法.我认为Java使用术语 PKCS#5和AES是一个msitake - 他们应该称之为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);
}
Run Code Online (Sandbox Code Playgroud)

(上面代码中的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....
Run Code Online (Sandbox Code Playgroud)

JS模块的输出:

key       : 2e0160e078aa4b925e62b20610378253
iv        : 7369787465656e62797465736c6f6e67
plaintext : AbbaDabbaDo_Once_upon_a_time....
ciphertext: f353e4dd6fb11ea13254dfef670ad88f8fbebcd24217374c06daefbbfe152df504035ae2d82537392c9ab1f719993ec1
decrypted : AbbaDabbaDo_Once_upon_a_time....
Run Code Online (Sandbox Code Playgroud)

和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);
Run Code Online (Sandbox Code Playgroud)

我对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++)
        {
        ....
Run Code Online (Sandbox Code Playgroud)

您可以在此处获取我使用的修改后的AES源以及我的测试程序.


重要提示:您不应该使用密码短语的MD5来获取密钥字节.使用PBKDF2.那里有一个java版的PBKDF2,它有效,并且有一个Javascript PBKDF2可以工作.此外,一些J2EE服务器包括PBKDF2类.同样对于IV字节.这些也应该来自密码.如果您对此有疑问,请阅读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个字节的填充,共加密48个字节.这是解密者可以做正确的事情.想一想:如果你不添加16个字节的填充,解密器就不能"告诉"明文的最后一个字节不是填充字节.

在这种情况下,SlowAES没有填充,这是你无法与Java进行互操作的部分原因.我注意到这一点,因为对于32字节的明文,cryptostream正好是32字节,这意味着没有填充.当我查看代码时,逻辑错误就在那里.(提醒我:我需要验证关于解密端填充的慢速中没有逻辑错误)

所以你的应用程序不知道未加密的长度是正确的.但是 如果使用PKCS#7填充,解密器确实知道要切掉多少字节,并且正确的解密器将始终返回正确的明文字节数.为了使其正常工作,您必须在解密时使用与加密时使用的填充约定相同的填充约定.你通常需要告诉加密库使用什么填充,虽然有些(如slowAES)没有给你选择.

如果在CBC模式下使用"无填充"这是某些库中的选项而不是在slowAES中,那么,是的,您的应用程序必须以某种方式"知道"未加密数据的大小,因此它可以丢弃最后N个字节纯文本.这在一些数据格式和协议中是可以的.但通常使用PKCS#7填充更容易.


编辑

再看一遍,是的,slowAES中的解密逻辑也存在填充问题.它希望你传递"解密长度" - 我现在看到,我看到这是你的问题的原因.如果它正确地执行PKCS#7填充,则这是不必要的.现在不是.应该是一个简单的修复.稍后会更新回来.

编辑

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

编辑

哦,还有一件事: 在Javascript中执行加密被认为是有害的.