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

Ionic离线加密数据无法通过AWS KMS解密,报InvalidCiphertextException

问题:离线AES加密文件无法通过AWS KMS解密,报错InvalidCiphertextException

背景

我正在开发一款支持离线工作、可离线加密数据的Ionic移动应用,已经写好了加密服务,用CryptoJS的AES算法加密文件并输出加密文件,加密功能本身正常。但尝试用AWS CLI调用KMS的Decrypt操作解密时,一直报错:An error occurred (InvalidCiphertextException) when calling the Decrypt operation:,怀疑是离线加密的算法逻辑有问题,求指出错误所在和解决方向。

加密服务代码(TypeScript)

import { Injectable } from '@angular/core';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import * as CryptoJS from 'crypto-js';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root',
})
export class EncryptionService {
  private encryptionKey: string;

  constructor() {
    // 初始化加密密钥(Base64编码字符串)
    this.encryptionKey = environment.encryptionKey;
  }

  // AES加密文件内容的函数
  public async encryptFile(fileName: string): Promise<void> {
    try {
      // 步骤1:读取文件内容
      const file = await Filesystem.readFile({
        path: fileName,
        directory: Directory.Documents,
        encoding: Encoding.UTF8, // 假设是文本文件(按需修改)
      });

      let fileContents: string;

      // 步骤2:确保文件内容是字符串格式
      if (typeof file.data === 'string') {
        fileContents = file.data;
      } else {
        throw new Error('不支持的文件格式,预期为字符串。');
      }

      // 步骤3:用AES加密内容
      const iv = CryptoJS.lib.WordArray.random(12);
      const encrypted = CryptoJS.AES.encrypt(fileContents, CryptoJS.enc.Base64.parse(this.encryptionKey), {
        iv: iv,
        format: CryptoJS.format.OpenSSL, // 使用OpenSSL格式
      });

      // 步骤4:合并IV和密文
      const combinedData = iv.concat(encrypted.ciphertext);
      const encryptedDataBase64 = CryptoJS.enc.Base64.stringify(combinedData);
      console.log('加密后的数据:', encryptedDataBase64);

      // 步骤5:将加密内容写入文件系统
      const encryptedFileName = `encrypted_${fileName}`;

      await Filesystem.writeFile({
        path: encryptedFileName,
        data: encryptedDataBase64,
        directory: Directory.Documents,
        encoding: Encoding.UTF8,
      });

      console.log('文件已加密并保存为:', encryptedFileName);
    } catch (error) {
      console.error('加密文件时出错:', error);
    }
  }
}

用于解密的AWS CLI命令

ENCRYPTED_DATA=$(cat encrypted_sample.txt)
DECRYPTED_DATA=$(aws kms decrypt --profile <profile-string-here> \
  --key-id <key-id-string-here> \
  --ciphertext-blob fileb://<(echo "$ENCRYPTED_DATA" | base64 --decode) \
  --region eu-west-1 \
  --query Plaintext \
  --output text)
echo "解密后的数据: $(echo "$DECRYPTED_DATA" | base64 --decode)"

错误原因分析

AWS KMS的Decrypt接口不是通用的AES解密工具,它只能处理两种情况的密文:

  1. 由KMS自身的Encrypt/GenerateDataKey等接口直接加密生成的密文
  2. 用KMS生成的数据密钥加密的内容,但需要配合KMS解密数据密钥的流程

你的代码是用本地自定义的AES密钥直接加密文件,生成的密文格式完全不符合KMS的要求——KMS无法识别这种没有关联元数据(比如密钥标识、加密上下文)的自定义密文,自然会抛出InvalidCiphertextException错误。

解决方向:采用信封加密模式

根据你离线加密的需求,正确的做法是使用信封加密,核心逻辑是:在线时用KMS生成数据密钥,离线时用本地数据密钥加密文件,解密时先通过KMS解密数据密钥,再用本地密钥解密文件。

调整后的加密流程(核心逻辑)

  1. 在线阶段:获取并加密数据密钥
    调用KMS的GenerateDataKey接口,得到明文数据密钥(用于本地加密)和加密后的密钥(存储起来,后续解密用)

    // 需引入AWS SDK for JavaScript
    import * as AWS from 'aws-sdk';
    
    async getEncryptedDataKey(): Promise<{ encryptedKey: string; plainKey: string }> {
      const kms = new AWS.KMS({ region: 'eu-west-1' });
      const response = await kms.generateDataKey({
        KeyId: '<你的KMS密钥ID>',
        KeySpec: 'AES_256' // 和CryptoJS使用的密钥长度匹配
      }).promise();
    
      // 转换为Base64方便存储
      return {
        encryptedKey: CryptoJS.enc.Base64.stringify(response.CiphertextBlob),
        plainKey: CryptoJS.enc.Base64.stringify(response.Plaintext)
      };
    }
    
  2. 离线阶段:用本地数据密钥加密文件
    保留你原有的AES加密逻辑,但改用KMS生成的明文数据密钥,同时推荐使用更安全的GCM模式:

    async encryptFileOffline(fileName: string, plainDataKey: string): Promise<void> {
      const file = await Filesystem.readFile({
        path: fileName,
        directory: Directory.Documents,
        encoding: Encoding.UTF8,
      });
      const fileContents = file.data as string;
    
      const iv = CryptoJS.lib.WordArray.random(12);
      const encrypted = CryptoJS.AES.encrypt(fileContents, CryptoJS.enc.Base64.parse(plainDataKey), {
        iv: iv,
        mode: CryptoJS.mode.GCM, // 推荐使用GCM认证加密模式
        padding: CryptoJS.pad.Pkcs7
      });
    
      // 合并IV、密文、GCM认证标签(用于解密时验证完整性)
      const combinedData = iv.concat(encrypted.ciphertext).concat(encrypted.authTag);
      const encryptedDataBase64 = CryptoJS.enc.Base64.stringify(combinedData);
    
      // 写入加密文件,同时要保存对应的加密后数据密钥
      await Filesystem.writeFile({
        path: `encrypted_${fileName}`,
        data: encryptedDataBase64,
        directory: Directory.Documents,
        encoding: Encoding.UTF8,
      });
    }
    

解密流程

  1. 读取加密文件和存储的加密后数据密钥
  2. 调用KMS Decrypt解密加密后的数据密钥,得到明文本地密钥
  3. 拆分加密文件中的IV、密文、认证标签
  4. 用明文密钥+IV+认证标签解密文件内容

关键注意事项

  • 绝对不要把KMS主密钥(CMK)直接用于本地加密,必须使用数据密钥做信封加密
  • 加密模式优先选GCM,比CBC更安全,且KMS支持该模式的数据密钥操作
  • 确保CryptoJS的加密参数(密钥长度、IV长度、填充模式)和KMS生成的数据密钥完全匹配

内容的提问来源于stack exchange,提问作者The Sammie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:04:51