Kotlin AES-GCM加密串在Angular中解密报错的解决求助
修复Angular中AES-GCM解密Kotlin加密字符串的错误
我是加密解密领域的新手,尝试在Angular(TypeScript)应用中使用Web Crypto解密由Kotlin通过AES-GCM算法加密的字符串,但解密失败并抛出错误:TypeError: Key data must be a BufferSource for non-JWK formats。以下是具体的加密、解密实现代码及示例数据:
Kotlin加密代码
import android.util.Base64 import java.io.IOException import java.security.* import java.security.spec.AlgorithmParameterSpec import javax.crypto.BadPaddingException import javax.crypto.Cipher import javax.crypto.IllegalBlockSizeException import javax.crypto.NoSuchPaddingException import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec class EncryptionUtils { companion object { private const val CHARACTER_ENCODING = "UTF-8" private const val CIPHER_TRANSFORMATION = "AES/GCM/NoPadding" private const val AES_ENCRYPTION_ALGORITHM = "AES" private const val GCM_NONCE_LENGTH = 12 private const val GCM_TAG_LENGTH = 128 fun encrypt(plain: String, key: String): String { val encryptedBytes = encrypt(plain.toByteArray(Charsets.UTF_8), key) return Base64.encodeToString(encryptedBytes, Base64.DEFAULT) } fun encrypt(message: ByteArray, key: String): ByteArray { val keyBytes = generateKey(key) val ivBytes = generateRandomIV(GCM_NONCE_LENGTH) val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION) val secretKey = SecretKeySpec(keyBytes, AES_ENCRYPTION_ALGORITHM) val gcmParameterSpec = GCMParameterSpec(GCM_TAG_LENGTH, ivBytes) cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec) val encrypted = cipher.doFinal(message) val result = ByteArray(ivBytes.size + encrypted.size) System.arraycopy(ivBytes, 0, result, 0, ivBytes.size) System.arraycopy(encrypted, 0, result, ivBytes.size, encrypted.size) return result } private fun generateKey(key: String): ByteArray { val keyBytes = key.toByteArray(Charsets.UTF_8) val md = MessageDigest.getInstance("SHA-256") md.update(keyBytes) return md.digest() } private fun generateRandomIV(length: Int): ByteArray { val ivBytes = ByteArray(length) val random = SecureRandom() random.nextBytes(ivBytes) return ivBytes } } }
private suspend fun sendErrorReportPostRequest(token: String, errorReport: ErrorReport): Boolean = withContext(Dispatchers.IO) { return@withContext try { val encryption = EncryptionUtils.Companion val key = "MYAPP${errorReport.file.nameWithoutExtension}" val api = ApiUploadDatabasePostRequest( s3Link = errorReport.file.name, password = encryption.encrypt("some-password" ?: "", key), ) result.isSuccess } catch (e: IOException) { Timber.e(e) false } }
加密示例数据
salt = "MYAPP" secretKey = "MYAPP11948-Sep022023145358" encrypted = 'a4b67Rufd5nODsjgdWllLlgxVJQewy+nLNZxlDM57JxA7NJvwv5Sfb26qYtkYY0JZF3iNaMuN6RBFANEXHE44w=='
原Angular解密代码
async decryptPass() { let secretKey = "MYAPP11948-Sep022023145358" const encryted = 'a4b67Rufd5nODsjgdWllLlgxVJQewy+nLNZxlDM57JxA7NJvwv5Sfb26qYtkYY0JZF3iNaMuN6RBFANEXHE44w==' const secretKeyBytes = this.generateKey(secretKey); const keyBuffer = await window.crypto.subtle.importKey( 'raw', secretKeyBytes, { name: 'AES-GCM', length: 128 }, false, ['decrypt'] ) const encryptedString = atob(encryted) const byteArray = new Uint8Array(encryptedString.length); for (let i = 0; i < encryptedString.length; i++) { byteArray[i] = encryptedString.charCodeAt(i); } const ivBytes = byteArray.slice(0, 12); const encryptedBytes = byteArray.slice(12); const cleartext = await crypto.subtle.decrypt({ name: 'AES-GCM', "iv": ivBytes, }, keyBuffer, encryptedBytes); console.log(new TextDecoder().decode(cleartext)) // Expected output: 8d818916-2cd3-4d72-aa7a-5c73783ba16a } generateKey(key: string) { const textEncoder = new TextEncoder(); const byteArray = textEncoder.encode(key); let md = forge.md.sha256.create(); md.update(byteArray); return md.digest() }
错误原因分析
- 密钥格式不兼容:
generateKey函数使用forge库生成SHA-256哈希,返回的是forge专属的Digest对象,而Web Crypto的importKey要求非JWK格式的密钥必须是BufferSource(如ArrayBuffer或Uint8Array),这是抛出错误的核心原因。 - 密钥长度配置错误:Kotlin中通过SHA-256生成的密钥是256位(32字节),但Angular代码中
importKey的配置写的是length: 128,长度不匹配会导致解密失败。 - Base64解码冗余:原代码手动循环转换字符为字节的方式可以用更简洁的API替代。
修复后的解密代码
async decryptPass() { const secretKey = "MYAPP11948-Sep022023145358"; const encrypted = 'a4b67Rufd5nODsjgdWllLlgxVJQewy+nLNZxlDM57JxA7NJvwv5Sfb26qYtkYY0JZF3iNaMuN6RBFANEXHE44w=='; // 改用Web Crypto生成SHA-256哈希,返回符合要求的ArrayBuffer const secretKeyBytes = await this.generateKey(secretKey); // 修正密钥长度为256位,匹配Kotlin端的SHA-256输出 const keyBuffer = await window.crypto.subtle.importKey( 'raw', secretKeyBytes, { name: 'AES-GCM', length: 256 }, false, ['decrypt'] ); // 简化Base64转Uint8Array的逻辑 const encryptedBytes = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0)); const ivBytes = encryptedBytes.slice(0, 12); const ciphertextWithTag = encryptedBytes.slice(12); const cleartext = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: ivBytes }, keyBuffer, ciphertextWithTag ); console.log(new TextDecoder().decode(cleartext)); // 输出:8d818916-2cd3-4d72-aa7a-5c73783ba16a } // 替换为Web Crypto原生API生成SHA-256哈希 async generateKey(key: string) { const textEncoder = new TextEncoder(); const keyBytes = textEncoder.encode(key); const hashBuffer = await window.crypto.subtle.digest('SHA-256', keyBytes); return hashBuffer; }
关键修复点说明
- 统一密钥生成方式:移除
forge库依赖,改用Web Crypto原生的subtle.digest生成SHA-256哈希,直接返回ArrayBuffer,完美匹配importKey要求的BufferSource类型。 - 对齐密钥长度:将
importKey中的length参数改为256,和Kotlin端SHA-256生成的密钥长度保持一致。 - 优化字节转换逻辑:使用
Uint8Array.from配合atob直接转换Base64字符串为字节数组,代码更简洁高效。
内容的提问来源于stack exchange,提问作者Aj James
相关产品推荐
相关产品推荐

