You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 11:18:43