Javascript< - > Java AES [英] Javascript <-> Java AES

查看:142
本文介绍了Javascript< - > Java AES的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个使用AES加密而不是AJAX的Web应用程序来与Java后端进行交互。

I'm attempting to write a web app that uses AES encryption over AJAX to interact with a Java backend.

我花了很长时间寻找和测试库并没有证明它们是富有成效的。

I've spent a long time looking for and testing libraries and none of them have proven fruitful.

我有Java< - > PHP正常使用以下java代码:

I have Java <-> PHP working correctly with the following java code:

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。

Base64 is org.apache.commons.codec.binary.Base64.

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

I tried SlowAES, but it didn't support "PKCS5Padding", but even if this was present the actual encryption may not have been working.

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

If anyone has any prior experience of Java <-> Javascript AES I'd be very grateful if they could impart some advice.

谢谢

推荐答案

我看着slowAes,我想你是正确的。它坏了。

I looked at slowAes, and I think you are correct. It's broken.

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

The code intends to apply PKCS#7 padding when operating in CBC mode, but does not succeed. (PKCS#7 is just PKCS#5 extended to a 16-byte block encryption algorithm. I think java's use of the term PKCS#5 with AES is a msitake - they should call it PKCS#7, because they are doing 16-byte padding).

我修改了slowAes以正确执行填充,并且通过该修改,我在slowAes和Java代码之间获得了良好的互操作性。但是您需要修改Java代码。稍后会详细介绍。

I modified slowAes to do the padding correctly, and with that modification, I got good interoperability between slowAes and your Java code. But you need to modify your Java code. More about that later.

这是我使用的java代码:(仅适用于演示;不适合在真实应用中使用

here's the java code I used: (Demonstration ONLY; not suitable for use in real apps)

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,因为我想实际看到字节。)

(the Hex class in the code above is org.apache.commons.codec.binary.Hex; It's in the same jar as the Base64 encoder you used. I swapped out your Base64 because I wanted to actually see the bytes.)

这里是输出:

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的新var,

The modification I made to slowAes was in the encrypt() function. I added a new var called 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源,此处,以及我的测试计划

You can get the modified AES source I used, here, along with my test program.

重要:您不应该使用MD5密码来获取密钥字节。使用PBKDF2。那里有一个java版的PBKDF2,它有效,并且有一个有效的Javascript PBKDF2 。此外,一些J2EE服务器包括PBKDF2类。同样对于IV字节。这些也应该来自密码。如果您对此有疑问,请阅读IETF RFC 2898 了解相关理由。

Important: you should not be using an MD5 of the passphrase to get the keybytes. Use PBKDF2. There's a java version of PBKDF2 out there, and it works, and there's a Javascript PBKDF2 that works. Also, some J2EE servers include a PBKDF2 class. Likewise for the IV bytes. These also should be derived from the passphrase. If you doubt this, read IETF RFC 2898 for the rationale.

不要将我上面发布的代码用于真实的应用程序。修改它以使用PBKDF2。

Don't use the code I posted above for a real app. Modify it to use PBKDF2.

编辑

关于填充......

EDIT
About padding...


在解密返回的消息时,如何删除额外的填充,因为我不一定知道未加密的长度。我认为填充字节意味着等于填充长度,但它们似乎不是。

How would I go about removing the additional padding when decrypting a returned message because I wouldn't necessarily know the unencrypted length. I thought that the padding bytes were meant to be equal to the padding length, but they don't seem to be.

AES是块加密器;它加密正好16字节长的块。如果你输入32个字节的明文,你会得到正好32字节的密码文本作为回报。如果你输入1024个字节,你会得到1024个字节。 (不完全正确,你会明白为什么以后。只是假设现在这是真的。)

AES is a block encryptor; it encrypts blocks of exactly 16 bytes long. If you put in 32 bytes of plaintext, you get exactly 32 bytes of cryptotext out in return. If you put in 1024 bytes, you get 1024 bytes out. (not exactly true, you'll see why later. just assume this is true for now.)

正如你所看到的,当明文不是偶数倍时16字节,因为AES 需要 16字节块,问题出现了 - 我将什么作为额外的东西来制作一个完整的16字节块?答案是填充。

As you have seen, when the plaintext is not an even multiple of 16 bytes, since AES requires 16 byte blocks, the question arises - what do I put in as "extra" stuff to make a full 16-byte block? The answer is padding.

垫子有不同之处。在CBC模式下,典型的方式是PKCS#7(Java称之为PKCS#5,正如我所说,我认为这是用词不当)。如果发送25个字节的明文,填充意味着AES实际上将加密32个字节:25个字节的实际数据和7个字节的填充。好的,但是填充7个字节的值是多少?

There are different was to pad. In CBC mode, the typical way is PKCS#7 (Java calls it PKCS#5, as I said I think that is a misnomer). If you send in 25 bytes of plaintext, padding means AES will actually encrypt 32 bytes: 25 bytes of actual data, and 7 bytes of padding. Ok, but what values go into the 7 bytes of padding?

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

PKCS#7 says that the pad byte is the value 16-len, where len is the length of actual data bytes in the final block. In other words, the value is the same as the number of pad bytes, which is what you said. In the example above, if you encrypt 25 bytes, you need 7 pad bytes, and each one will take the value 7. These pad bytes get added to the end of the plaintext, before encryption. It results in a cryptostream that is a whole number of 16-byte blocks.

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

This is nice because on decryption, the decryptor can simply look at the final byte in the decrypted stream, and it now knows how many pad bytes to remove from that decrypted stream. With PKCS#7 padding, the application layer does not need to worry about removing padding on decryption or adding padding on encryption. The AES library should handle all that. Suppose the decryptor decrypts 32 bytes of cryptotext, and the last byte in the resulting plaintext is a value of 7. With PKCS#7 padding, the decryptor knows to slice off 7 bytes from the end of the last block, and deliver a partial block of 9 bytes for the final one, to the application, for a total of 25 bytes of plaintext.

Java正确执行此操作。 slowAES正确地做了,除了明文长度是16字节的倍数的情况。 PKCS#7表示在这种情况下,你需要添加16个字节的填充,所有填充值都为16.如果你想加密正好32个字节,AES的AESCS#7说,你需要添加16个字节的填充,共加密48个字节。这是解密者可以做正确的事情。想一想:如果你不添加16字节的填充,解密器就不能告诉明文的最后一个字节不是填充字节。

Java does this correctly. slowAES was doing it correctly, except for the case where the plaintext length was a multiple of 16 bytes. PKCS#7 says that in that case, you need to add 16 bytes of padding, all with value 16. If you want to encrypt exactly 32 bytes, PKCS#7 for AES says, you need to add 16 bytes of pad, for a total of 48 bytes encrypted. This is so the decryptor can do the right thing. Think about it: If you don't add 16 bytes of padding, the decryptor cannot "tell" that the last byte of plaintext is not a padding byte.

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

SlowAES was not padding in this case, which was part of the reason you couldn't get it to interoperate with Java. I noticed this because the cryptostream was exactly 32 bytes for a 32-byte plaintext, and that means no padding. When I looked in the code the logic error was right there. (Reminds me: I need to verify that there is not a logic error in slowaes regarding padding on the decryption side)

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

So you are correct that your app does not know the unencrypted length. But the decryptor does know how many bytes to slice off, if PKCS#7 padding is used, and a correct decryptor will always return to you the correct number of bytes of plaintext. For this to work properly, you must use the same padding convention when decrypting as was used when encrypting. You normally need to tell the crypto library what padding to use, although some (like slowAES) don't give you the choice.

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

If you use "no padding" which is an option in some libraries but not in slowAES when in CBC mode, then, yes, your application must somehow "know" the size of the unencrypted data, so it can discard the last N bytes of plaintext. This is ok in some data formats and protocols. But often it's easier to use PKCS#7 padding.

编辑

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

just looked again and yes, the decrypt logic in slowAES also has a problem with the padding. It wants you to pass in the "decrypted length" - I see that now, I see that was the reason for your question. This is unnecessary if it does PKCS#7 padding properly. It isn't now. Should be a simple fix. Will update back here, later.

编辑

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

ok, the updated AES file is now available here; the updated test code is here. It does PKCS#7 padding correctly on encrypt and decrypt. I probably should send these changes back to the owners of slowAES.

编辑

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

这篇关于Javascript&lt; - &gt; Java AES的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆