最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - CryptoJS.AES.encrypt() and Java AES encryption producing different results - Stack Overflow

programmeradmin0浏览0评论

I'm trying to encrypt data using AES in both JavaScript (with CryptoJS) and Java/Scala, but the Java encryption cannot be decrypted by a service while the CryptoJS version works correctly.

Here's my working CryptoJS code:

const encrypted = CryptoJS.AES.encrypt(data, secret);

And here's my Java/Scala implementation that's not working:

val sha256 = java.security.MessageDigest.getInstance("SHA-256")
val key = new SecretKeySpec(sha256.digest(secret.getBytes("UTF-8")), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val iv = new Array[Byte](16)
new java.security.SecureRandom().nextBytes(iv)
val ivSpec = new IvParameterSpec(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
val encrypted = cipher.doFinal(data.getBytes("UTF-8"))
val combined = iv ++ encrypted
Base64.getEncoder.encodeToString(combined)

Expected Behavior:

Both implementations should produce encrypted data that the service can decrypt

Actual Behavior:

CryptoJS encryption works and can be decrypted Java/Scala encryption cannot be decrypted by the service

Questions

  • What are the default settings used by CryptoJS.AES.encrypt()?
  • How can I modify my Java/Scala code to match CryptoJS's encryption format?

I'm trying to encrypt data using AES in both JavaScript (with CryptoJS) and Java/Scala, but the Java encryption cannot be decrypted by a service while the CryptoJS version works correctly.

Here's my working CryptoJS code:

const encrypted = CryptoJS.AES.encrypt(data, secret);

And here's my Java/Scala implementation that's not working:

val sha256 = java.security.MessageDigest.getInstance("SHA-256")
val key = new SecretKeySpec(sha256.digest(secret.getBytes("UTF-8")), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val iv = new Array[Byte](16)
new java.security.SecureRandom().nextBytes(iv)
val ivSpec = new IvParameterSpec(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
val encrypted = cipher.doFinal(data.getBytes("UTF-8"))
val combined = iv ++ encrypted
Base64.getEncoder.encodeToString(combined)

Expected Behavior:

Both implementations should produce encrypted data that the service can decrypt

Actual Behavior:

CryptoJS encryption works and can be decrypted Java/Scala encryption cannot be decrypted by the service

Questions

  • What are the default settings used by CryptoJS.AES.encrypt()?
  • How can I modify my Java/Scala code to match CryptoJS's encryption format?
Share Improve this question asked Feb 4 at 11:29 MichaelMichael 4,3937 gold badges35 silver badges70 bronze badges 5
  • 2 A1: cryptojs.gitbook.io/docs <- AES/CBC/PKCS7Padding + mystery key & IV derivation from passphrase – teapot418 Commented Feb 4 at 11:47
  • re Q2: do you really want exactly that or would it be enough to change both to be interoperable? (like specifying key & iv on the JS side) – teapot418 Commented Feb 4 at 11:50
  • 1 What type is secret in the JS code? The processing logic depends on this. If it is a string (which is probably true), EVP_BytesToKey() is used to derive key and IV. You should post the complete JS code for a repro or at least test data. – Topaco Commented Feb 4 at 11:52
  • @teapot418 I just know that passing in the same data and secret into the JS version is decrypted fine by the service we call, but I've been unable to generate it in Java/Scala and have it decrypt. – Michael Commented Feb 4 at 11:57
  • So you need a java implementation of EVP_BytesToKey. Searching for that gave me this (untested, quality unknown). – teapot418 Commented Feb 4 at 12:14
Add a comment  | 

1 Answer 1

Reset to default 3

If the key material is passed as a string in CryptoJS, CryptoJS generates a random 8 bytes salt during encryption and derives a 32 bytes key and 16 bytes IV using the OpenSSL proprietary key derivation function EVP_BytesToKey(). For this, an iteration count of 1 and the digest MD5 is applied.

This key derivation is missing in your Scala Code. You can find various implementations of EVP_BytesToKey() on the web. Since not all of the functionality is required for the key/IV derivation in the context of CryptoJS, a lightweight implementation may also be enough, e.g:

def getKeyIv(password: Array[Byte], salt: Array[Byte]): (Array[Byte], Array[Byte]) = {
  var key, iv, last = Array[Byte]()
  val md = MessageDigest.getInstance("MD5")
  while (key.length < 32) {
      last = md.digest(last ++ password ++ salt)
      key = key ++ last
  }
  iv = md.digest(last ++ password ++ salt)
  (key, iv)
} 

CryptoJS.AES.encrypt() returns the result as a CipherParams object whose toString() function is overloaded to return the result in the Base64 encoded OpenSSL format:

val ciphertextOpenSSLFrmt = "Salted__".getBytes(StandardCharsets.UTF_8) ++ salt ++ ciphertext 

Security: Note that EVP_BytesToKey() is considered insecure nowadays (PBKDF2, which is also supported by CryptoJS, is more secure).


Test:

Due to the random salt, a different key and IV and therefore a different ciphertext are generated for each encryption. The implementation can therefore not be checked by comparing the results, but by checking whether decryption, e.g. with CryptoJS, is successful.

Sample implementation for encryption:

import java.security.{SecureRandom, MessageDigest}
import javax.crypto.Cipher
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}
import java.nio.charset.StandardCharsets
import java.util.Base64
...
val plaintext: Array[Byte] = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8)
val password: Array[Byte] = "my password".getBytes(StandardCharsets.UTF_8)
val secureRandom = new SecureRandom() // create random salt
val salt: Array[Byte] = new Array[Byte](8)
secureRandom.nextBytes(salt)
val (key, iv) = getKeyIv(password, salt) // derive key and IV
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") // encrypt
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv))
val ciphertext = cipher.doFinal(plaintext)
val ciphertextOpenSSLFrmt = "Salted__".getBytes(StandardCharsets.UTF_8) ++ salt ++ ciphertext // concatenate result 
println(Base64.getEncoder.encodeToString(ciphertextOpenSSLFrmt)) // sample output: U2FsdGVkX1/joJPOllaIZiV+ehbDM5KP4IAn/DtCl3Uvfi+8BA8kyznFD+gZJfjFMhz7dD2ke94BR9mvrKTF0Q==  

Decryption with CryptoJS:

const decrypted = CryptoJS.AES.decrypt("U2FsdGVkX1/joJPOllaIZiV+ehbDM5KP4IAn/DtCl3Uvfi+8BA8kyznFD+gZJfjFMhz7dD2ke94BR9mvrKTF0Q==", "my password")
console.log(decrypted.toString(CryptoJS.enc.Utf8))
<script src="https://cdnjs.cloudflare/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>

发布评论

评论列表(0)

  1. 暂无评论