Angular与Spring Boot两端DES加密解密不兼容问题求助
问题根因
两端加解密不兼容的核心原因是密钥派生逻辑、算法参数、编码规则三个层面均未对齐,具体如下:
- 密钥派生规则不一致:
CryptoJS的DES.encrypt默认使用OpenSSL标准的EVP_BytesToKey算法派生真实DES密钥,哈希算法为MD5、迭代次数固定为1,且加密时会自动生成随机8字节盐,生成的密文以Salted__为前缀标识;而Java端使用固定硬编码的盐、迭代次数为3,基于PBEKeySpec派生密钥,两端生成的真实DES密钥完全不同,自然会出现BadPadding异常。 - 编码规则不一致:
Java加密返回时使用encodeBase64URLSafeString生成URL安全的Base64编码,会将+替换为-、/替换为_且去掉末尾=填充,而CryptoJS默认输出标准Base64编码,编码规则不匹配也会导致解密时数据解析错误。 - 加密模式与填充规则未显式对齐:
CryptoJS的DES默认使用CBC模式、PKCS7填充,IV向量从密码和盐共同派生,若Java端的PBE算法参数未和该规则匹配,也会导致解密失败。
解决方案
推荐选择以下任意一种方案对齐两端规则即可:
方案1:修改Angular端逻辑对齐Java端
Angular端不再使用默认的加密接口,手动实现和Java端一致的密钥派生规则,指定固定盐、迭代次数3、算法为PBEWithMD5AndDES,示例代码如下:
convertText() { const text = this.textToConvert.trim(); const pwd = this.password.trim(); if (!text || !pwd) { this.conversionOutput = 'Please fill the textboxes.'; return; } // 与Java端一致的固定盐 const salt = CryptoJS.lib.WordArray.create([0xA99BC832, 0x5635E303]); const key = CryptoJS.PBKDF2(pwd, salt, { keySize: 64/32, iterations: 3, hasher: CryptoJS.algo.MD5 }); // PBEWithMD5AndDES的IV派生规则和Java对齐 const iv = CryptoJS.lib.WordArray.create([0x00000000, 0x00000000]); if (this.encryptMode) { const encrypted = CryptoJS.DES.encrypt(text, key, { iv: iv, padding: CryptoJS.pad.PKCS5, mode: CryptoJS.mode.CBC }); // 输出URL安全Base64和Java对齐 this.conversionOutput = encrypted.ciphertext.toString(CryptoJS.enc.Base64) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); } else { // 先把URL安全Base64转成标准Base64 const standardBase64 = text.replace(/-/g, '+').replace(/_/g, '/'); const decrypted = CryptoJS.DES.decrypt(standardBase64, key, { iv: iv, padding: CryptoJS.pad.PKCS5, mode: CryptoJS.mode.CBC }); this.conversionOutput = decrypted.toString(CryptoJS.enc.Utf8); } }
方案2:修改Java端逻辑对齐CryptoJS默认规则
Java端解析CryptoJS生成的密文中的随机盐,使用和CryptoJS一致的EVP_BytesToKey派生密钥和IV,示例核心代码如下:
// 解密CryptoJS生成的DES密文 public static String decryptForCryptoJs(String cipherText, String password) throws Exception { byte[] cipherBytes = Base64.decodeBase64(cipherText); // 提取CryptoJS密文中的盐:前8字节是Salted__,第9-16字节是盐 byte[] salt = Arrays.copyOfRange(cipherBytes, 8, 16); byte[] realCipher = Arrays.copyOfRange(cipherBytes, 16, cipherBytes.length); // 实现EVP_BytesToKey派生密钥和IV,迭代次数1,MD5哈希 byte[] keyAndIv = evpBytesToKey(password.getBytes(StandardCharsets.UTF_8), salt, 48, 1); byte[] key = Arrays.copyOfRange(keyAndIv, 0, 8); byte[] iv = Arrays.copyOfRange(keyAndIv, 8, 16); // DES/CBC/PKCS5Padding解密 Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "DES"), new IvParameterSpec(iv)); byte[] result = cipher.doFinal(realCipher); return new String(result, StandardCharsets.UTF_8); } // 实现OpenSSL的EVP_BytesToKey算法 private static byte[] evpBytesToKey(byte[] password, byte[] salt, int length, int iterations) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] result = new byte[length]; int pos = 0; byte[] digest = new byte[0]; while (pos < length) { md.update(digest); md.update(password); md.update(salt); digest = md.digest(); for (int i = 1; i < iterations; i++) { digest = md.digest(digest); } int copyLen = Math.min(digest.length, length - pos); System.arraycopy(digest, 0, result, pos, copyLen); pos += copyLen; } return result; }
修改后调用decryptForCryptoJs方法即可正常解密Angular端生成的密文。
内容的提问来源于stack exchange,提问作者user9634982
相关产品推荐
相关产品推荐

