使用KMS解密S3中AWS SES加密消息时遇认证错误
解密S3中KMS加密的AWS SES消息时遇认证错误
我尝试解密存储在S3里、由KMS加密的AWS SES消息,目前能正常从S3获取对象和元数据,也能用KMS解密密钥,但在调用decipher.final()时触发了Unsupported state or unable to authenticate data错误,初步判断是authTag不匹配或者编解码方式不对。
问题代码
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { KMSClient, DecryptCommand } from '@aws-sdk/client-kms'; import { createDecipheriv } from 'crypto'; const s3 = new S3Client( { // ...credentials } ); const kms = new KMSClient( { // ...credentials } ); const getFromS3AndDecrypt = ( Bucket, Key ) => { // GetObject command const command = new GetObjectCommand( { Bucket, Key, } ); // Get the object from S3 const { Metadata, Body } = await s3.send( command ); // Convert to Buffer const body = await streamToBuffer( Body ); // Get KMS Metadata var { [ 'x-amz-key-v2' ]: kmsKeyBase64, [ 'x-amz-iv' ]: iv, [ 'x-amz-tag-len' ]: tagLenBits = 0, [ 'x-amz-cek-alg' ]: algo, [ 'x-amz-matdesc' ]: matdesc, } = Metadata; // Convert tagLenBits to Bytes const tagLen = tagLenBits / 8; // Get encryption context const encryptionContext = JSON.parse( matdesc ); // Get algorithm switch ( algo ) { case 'AES/GCM/NoPadding': algo = `aes-256-gcm`; break; case 'AES/CBC/PKCS5Padding': algo = `aes-256-cbc`; break; default: throw new Error( `Unsupported algorithm ${ algo }` ); } // Convert kmsKey to Buffer const kmsKeyBuffer = Buffer.from( kmsKeyBase64, 'base64' ); // DecryptCommand const kmsCommand = new DecryptCommand( { CiphertextBlob: kmsKeyBuffer, EncryptionContext: encryptionContext, } ); // Decrypt the key with KMS const { Plaintext } = await kmsClient.send( kmsCommand ); // Create decipher with key and iv const decipher = createDecipheriv( algo, Buffer.from( Plaintext ), Buffer.from( iv ), { authTagLength: 16, } ); // Body without authTag const data = body.slice( 0, - tagLen ); if ( tagLen !== 0 ) { // authTag const tag = body.slice( - tagLen ); // Set authTag decipher.setAuthTag( tag ); } // Decrypt data var decrypted = decipher.update( data, 'binary', 'utf8' ); decrypted += decipher.final( 'utf8' ); return decrypted; }; // Turn @aws-sdk/client-s3 stream response into buffer const streamToBuffer = ( stream ) => new Promise( ( resolve, reject ) => { const chunks = []; stream.on( 'data', ( chunk ) => chunks.push( chunk ) ); stream.on( 'error', reject ); stream.on( 'end', () => resolve( Buffer.concat( chunks ) ) ); } );
核心问题与修复方案
以下是导致认证错误的关键问题及修正:
- IV未做Base64解码:S3元数据中的
x-amz-iv是Base64编码值,原代码直接转Buffer会导致IV值错误,进而使authTag验证失败。修正为Buffer.from(iv, 'base64')。 - authTagLength固定值不匹配:不要硬写
authTagLength:16,应使用计算出的tagLen,确保和加密时的标签长度一致。修正为authTagLength: tagLen || 16。 - 编解码方式错误:
decipher.update指定'binary'编码会导致字节流损坏,直接操作Buffer更可靠,最后统一转UTF-8字符串。 - 变量名与关键字缺失:函数
getFromS3AndDecrypt未加async关键字,直接使用await会报错;调用KMS解密时用了未定义的kmsClient,实际实例是kms,需统一变量名。
修复后的完整代码
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { KMSClient, DecryptCommand } from '@aws-sdk/client-kms'; import { createDecipheriv } from 'crypto'; const s3 = new S3Client( { // ...credentials } ); const kms = new KMSClient( { // ...credentials } ); const getFromS3AndDecrypt = async ( Bucket, Key ) => { const command = new GetObjectCommand( { Bucket, Key } ); const { Metadata, Body } = await s3.send( command ); const body = await streamToBuffer( Body ); var { [ 'x-amz-key-v2' ]: kmsKeyBase64, [ 'x-amz-iv' ]: iv, [ 'x-amz-tag-len' ]: tagLenBits = 0, [ 'x-amz-cek-alg' ]: algo, [ 'x-amz-matdesc' ]: matdesc, } = Metadata; const tagLen = tagLenBits / 8; const encryptionContext = JSON.parse( matdesc ); switch ( algo ) { case 'AES/GCM/NoPadding': algo = `aes-256-gcm`; break; case 'AES/CBC/PKCS5Padding': algo = `aes-256-cbc`; break; default: throw new Error( `Unsupported algorithm ${ algo }` ); } const kmsKeyBuffer = Buffer.from( kmsKeyBase64, 'base64' ); const kmsCommand = new DecryptCommand( { CiphertextBlob: kmsKeyBuffer, EncryptionContext: encryptionContext, } ); const { Plaintext } = await kms.send( kmsCommand ); const decipher = createDecipheriv( algo, Buffer.from( Plaintext ), Buffer.from( iv, 'base64' ), { authTagLength: tagLen || 16, } ); const data = body.slice( 0, - tagLen ); if ( tagLen !== 0 ) { const tag = body.slice( - tagLen ); decipher.setAuthTag( tag ); } let decrypted = decipher.update( data ); decrypted = Buffer.concat([decrypted, decipher.final()]).toString('utf8'); return decrypted; }; const streamToBuffer = ( stream ) => new Promise( ( resolve, reject ) => { const chunks = []; stream.on( 'data', ( chunk ) => chunks.push( chunk ) ); stream.on( 'error', reject ); stream.on( 'end', () => resolve( Buffer.concat( chunks ) ) ); } );
内容的提问来源于stack exchange,提问作者Snowball
相关产品推荐
相关产品推荐

