加密技能Skill encryption

这个技能提供了在JARVIS AI助手中实现加密的安全模式,涵盖数据加密、密钥派生、密钥管理、内存保护,并遵循严格的安全标准和测试驱动开发。关键词:加密、安全、密码学、AES-256-GCM、Argon2id、密钥管理、SQLCipher、TDD、性能优化。

密码学 0 次安装 0 次浏览 更新于 3/15/2026

加密技能


名称: 加密 版本: 1.0.0 领域: 安全/密码学 风险等级: 高 语言: [python, typescript, rust, go] 框架: [sqlcipher, cryptography, libsodium] 需要安全审查: 是 合规: [GDPR, HIPAA, PCI-DSS, SOC2] 最后更新: 2025-01-15

强制阅读协议: 在实施任何加密之前,阅读 references/advanced-patterns.md 获取密钥派生信息,以及 references/security-examples.md 获取实现模式。

1. 概述

1.1 目的和范围

此技能为 JARVIS AI 助手提供了安全默认的实现加密模式,涵盖:

  • SQLCipher: 使用 AES-256-GCM 的加密 SQLite 数据库
  • Argon2id: 内存硬度密钥派生函数
  • 密钥管理: 安全生成、存储、轮换和销毁
  • 安全内存: 防止内存泄露攻击

1.2 风险评估

风险等级: 高

理由:

  • 加密失败会暴露所有受保护数据
  • 密钥泄露导致完全机密性丧失
  • 实现错误是灾难性的且往往无法检测
  • 违反法规(GDPR、HIPAA、PCI-DSS)会带来严重处罚

攻击面:

  • 密钥派生弱点
  • 不安全随机数生成
  • 时序侧信道
  • 内存泄露(冷启动、崩溃转储)
  • 跨上下文密钥重用

2. 核心职责

2.1 主要功能

  1. 加密静态数据 使用 AES-256-GCM 进行认证加密
  2. 安全派生密钥 使用 Argon2id 和适当参数
  3. 管理密钥生命周期 包括轮换、托管和销毁
  4. 保护密钥材料 在内存中和操作期间
  5. 集成操作系统密钥链 用于主密钥存储

2.2 核心原则

  1. 测试驱动开发优先 - 先写测试;测试加密/解密往返、认证失败和边缘情况
  2. 性能意识 - 缓存派生密钥,对大数据使用流处理,利用硬件加速
  3. 安全默认 - 使用认证加密模式、内存硬度 KDF、安全随机源
  4. 深度防御 - 多层保护,安全失败,最小化密钥暴露

2.3 安全原则

  • 绝不 实现自定义加密算法
  • 绝不 使用 ECB 模式或未认证加密
  • 始终 使用加密安全随机数生成器
  • 始终 在解密前验证密文真实性
  • 始终 使用恒定时间比较认证标签

3. 实现工作流(TDD)

步骤 1: 先写失败测试

import pytest
from cryptography.exceptions import InvalidTag

class TestEncryptionTDD:
    """TDD 测试加密实现。"""

    def test_encrypt_decrypt_roundtrip(self):
        """测试加密后解密返回原始数据。"""
        from jarvis.security.encryption import SecureEncryption

        key = secrets.token_bytes(32)
        encryptor = SecureEncryption(key)

        plaintext = b"sensitive data for JARVIS"
        ciphertext = encryptor.encrypt(plaintext)
        decrypted = encryptor.decrypt(ciphertext)

        assert decrypted == plaintext
        assert ciphertext != plaintext  # 必须加密

    def test_tampered_ciphertext_raises_error(self):
        """测试篡改的密文被拒绝。"""
        from jarvis.security.encryption import SecureEncryption

        key = secrets.token_bytes(32)
        encryptor = SecureEncryption(key)

        ciphertext = encryptor.encrypt(b"secret")
        tampered = ciphertext[:-1] + bytes([ciphertext[-1] ^ 0xFF])

        with pytest.raises(InvalidTag):
            encryptor.decrypt(tampered)

    def test_key_derivation_consistency(self):
        """相同密码 + 盐 = 相同密钥;不同盐 = 不同密钥。"""
        from jarvis.security.encryption import SecureKeyDerivation
        password = "strong_password_123"
        salt = secrets.token_bytes(16)
        key1, _ = SecureKeyDerivation.derive_key(password, salt)
        key2, _ = SecureKeyDerivation.derive_key(password, salt)
        assert key1 == key2 and len(key1) == 32

        key3, salt3 = SecureKeyDerivation.derive_key(password)
        assert key1 != key3  # 不同盐 = 不同密钥

步骤 2: 实现最小通过

只实现通过测试所需的内容。从基本加密/解密开始,然后添加密钥派生。

步骤 3: 遵循模式重构

测试通过后,添加:内存保护、错误处理、AAD 支持、密钥缓存。

步骤 4: 运行完整验证

# 运行加密测试并覆盖
pytest tests/security/test_encryption.py -v --cov=jarvis.security.encryption --cov-fail-under=90

# 运行安全特定测试
pytest tests/security/ -k "encryption or crypto" -v

# 检查时序漏洞
pytest tests/security/test_timing.py -v

# 验证输出中无秘密
pytest --log-cli-level=DEBUG 2>&1 | grep -i "key\|secret\|password" && echo "WARNING: Secrets in logs!"

4. 技术栈

4.1 推荐库

语言 版本 备注
Python cryptography >=42.0.0 使用 OpenSSL 3.x 后端
Python argon2-cffi >=23.1.0 参考 Argon2 实现
TypeScript @noble/ciphers >=0.5.0 审计过的纯 JS 实现
Rust ring >=0.17.0 BoringSSL 支持
Go crypto/cipher 标准库 golang.org/x/crypto 使用

4.2 SQLCipher 配置

最小版本: SQLCipher 4.5.6+(包括 SQLite 3.44.2)

# SQLCipher 安全配置
SQLCIPHER_PRAGMAS = {
    'key': None,  # 通过安全密钥注入设置
    'cipher': 'aes-256-gcm',
    'kdf_iter': 256000,  # PBKDF2 迭代
    'cipher_page_size': 4096,
    'cipher_kdf_algorithm': 'PBKDF2_HMAC_SHA512',
    'cipher_hmac_algorithm': 'HMAC_SHA512',
    'cipher_plaintext_header_size': 0,
}

5. 性能模式

5.1 密钥缓存

坏: 每次操作派生密钥(约 500ms 每次 Argon2id 调用)

好 - 缓存带 TTL:

class CachedKeyManager:
    def __init__(self, cache_ttl: int = 300):
        self._cache: dict[str, tuple[bytes, float]] = {}
        self._ttl = cache_ttl

    def get_key(self, password: str, salt: bytes) -> bytes:
        cache_key = f"{hash(password)}:{salt.hex()}"
        if cache_key in self._cache:
            key, ts = self._cache[cache_key]
            if time.time() - ts < self._ttl:
                return key
        key, _ = SecureKeyDerivation.derive_key(password, salt)
        self._cache[cache_key] = (key, time.time())
        return key

5.2 大数据的流加密

坏: data = f.read() 将整个文件加载到内存

好 - 流处理带分块(64KB 块):

nonce = secrets.token_bytes(12)
encryptor = Cipher(algorithms.AES(key), modes.GCM(nonce)).encryptor()
with open(input_path, 'rb') as fin, open(output_path, 'wb') as fout:
    fout.write(nonce)
    while chunk := fin.read(64 * 1024):
        fout.write(encryptor.update(chunk))
    fout.write(encryptor.finalize() + encryptor.tag)

5.3 硬件加速

坏: 无 OpenSSL 后端的 PyCryptodome(慢 10-100 倍)

好: 使用 cryptography 库 - 通过 OpenSSL 3.x 后端自动检测 AES-NI

5.4 批量操作

坏 - 带附加的单个循环:

results = []
for record in records:
    results.append(encryptor.encrypt(record))

好 - 使用单个加密器的列表推导:

encryptor = SecureEncryption(key)
results = [encryptor.encrypt(record) for record in records]

# 对于大批量,使用 ProcessPoolExecutor 进行并行化

5.5 内存安全密钥处理

坏 - 密钥保留在内存中:

self.key = SecureKeyDerivation.derive_key(password)  # 从未清除

好 - 使用后归零密钥的上下文管理器:

import ctypes

class SecureKeyHolder:
    def __init__(self, password: str):
        self._key, self.salt = SecureKeyDerivation.derive_key(password)

    def __exit__(self, *args):
        if self._key:
            key_buffer = (ctypes.c_char * len(self._key)).from_buffer_copy(self._key)
            ctypes.memset(key_buffer, 0, len(self._key))
            self._key = None

# 用法: with SecureKeyHolder(password) as kh: encrypt(kh._key, data)

6. 实现模式

6.1 使用 Argon2id 派生密钥

from argon2 import PasswordHasher
from argon2.low_level import hash_secret_raw, Type
import secrets

class SecureKeyDerivation:
    """使用 Argon2id 从密码派生加密密钥。"""

    # OWASP 推荐参数用于敏感数据
    TIME_COST = 3        # 迭代次数
    MEMORY_COST = 65536  # 64 MiB
    PARALLELISM = 4      # 线程数
    HASH_LEN = 32        # 256 位用于 AES-256
    SALT_LEN = 16        # 128 位最小

    @classmethod
    def derive_key(cls, password: str, salt: bytes = None) -> tuple[bytes, bytes]:
        """
        从密码派生 256 位密钥。

        返回:
            tuple: (派生密钥, 盐) 用于存储
        """
        if salt is None:
            salt = secrets.token_bytes(cls.SALT_LEN)

        # 验证输入
        if not password or len(password) < 12:
            raise ValueError("密码必须至少 12 个字符")

        key = hash_secret_raw(
            secret=password.encode('utf-8'),
            salt=salt,
            time_cost=cls.TIME_COST,
            memory_cost=cls.MEMORY_COST,
            parallelism=cls.PARALLELISM,
            hash_len=cls.HASH_LEN,
            type=Type.ID  # Argon2id
        )

        return key, salt

6.2 AES-256-GCM 加密

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import secrets

class SecureEncryption:
    """AES-256-GCM 认证加密。"""

    NONCE_SIZE = 12  # 96 位推荐用于 GCM
    KEY_SIZE = 32    # 256 位

    def __init__(self, key: bytes):
        if len(key) != self.KEY_SIZE:
            raise ValueError(f"密钥必须为 {self.KEY_SIZE} 字节")
        self._aesgcm = AESGCM(key)

    def encrypt(self, plaintext: bytes, associated_data: bytes = None) -> bytes:
        """
        使用随机 nonce 加密,前置到密文。

        返回:
            bytes: nonce || 密文 || 标签
        """
        nonce = secrets.token_bytes(self.NONCE_SIZE)
        ciphertext = self._aesgcm.encrypt(nonce, plaintext, associated_data)
        return nonce + ciphertext

    def decrypt(self, ciphertext: bytes, associated_data: bytes = None) -> bytes:
        """
        解密并验证真实性。

        抛出:
            InvalidTag: 如果认证失败
        """
        if len(ciphertext) < self.NONCE_SIZE + 16:  # nonce + 标签最小
            raise ValueError("密文太短")

        nonce = ciphertext[:self.NONCE_SIZE]
        actual_ciphertext = ciphertext[self.NONCE_SIZE:]

        return self._aesgcm.decrypt(nonce, actual_ciphertext, associated_data)

6.3 SQLCipher 数据库集成

import sqlcipher3
from contextlib import contextmanager

class EncryptedDatabase:
    """使用 SQLCipher 的加密 SQLite 数据库。"""

    def __init__(self, db_path: str, key: bytes):
        self._db_path = db_path
        self._key = key
        self._conn = None

    @contextmanager
    def connect(self):
        """数据库连接的上下文管理器。"""
        conn = sqlcipher3.connect(self._db_path)
        try:
            # 应用安全编译指示
            conn.execute(f"PRAGMA key = \"x'{self._key.hex()}'\";")
            conn.execute("PRAGMA cipher = 'aes-256-gcm';")
            conn.execute("PRAGMA kdf_iter = 256000;")
            conn.execute("PRAGMA cipher_page_size = 4096;")

            # 验证加密激活
            result = conn.execute("PRAGMA cipher_version;").fetchone()
            if not result:
                raise RuntimeError("SQLCipher 加密未激活")

            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def rekey(self, new_key: bytes):
        """轮换数据库加密密钥。"""
        with self.connect() as conn:
            conn.execute(f"PRAGMA rekey = \"x'{new_key.hex()}'\";")
        self._key = new_key

7. 安全标准

7.1 已知漏洞

CVE 严重性 组件 描述 缓解措施
CVE-2020-27207 SQLCipher <4.4.1 编解码编译指示中的使用后释放 升级到 4.5.6+
CVE-2024-0232 SQLite <3.44.0 JSON 中的堆使用后释放 升级 SQLCipher 4.5.6+
CVE-2023-42811 aes-gcm (Rust) 认证失败时的明文暴露 升级到 0.10.3+
CVE-2024-4603 OpenSSL 密钥派生时序攻击 升级 OpenSSL 3.3+
CVE-2023-48056 加密库 IV 重用检测失败 使用随机 nonce

7.2 OWASP 映射

OWASP 2025 相关性 实现
A02: 加密失败 关键 AES-256-GCM、Argon2id、安全 RNG
A04: 不安全设计 威胁建模、密钥轮换
A05: 安全配置错误 安全默认、验证
A08: 软件完整性失败 认证加密

7.3 加密标准

批准算法:

  • 对称: AES-256-GCM(主要)、ChaCha20-Poly1305(替代)
  • KDF: Argon2id(主要)、PBKDF2-HMAC-SHA512(SQLCipher)
  • 哈希: SHA-256、SHA-512、BLAKE2b
  • RNG: 仅操作系统 CSPRNG(secrets 模块、/dev/urandom

禁止:

  • DES、3DES、RC4、Blowfish
  • MD5、SHA-1 用于安全目的
  • 任何密码的 ECB 模式
  • 自定义随机数生成器

8. 测试要求

参见第 3 节(实现工作流 - TDD)获取综合测试示例,包括:

  • 加密/解密往返
  • 密文篡改检测
  • 密钥派生一致性
  • Nonce 唯一性验证

9. 常见错误

9.1 关键反模式

反模式 绝不这样做 始终这样做
ECB 模式 modes.ECB() AESGCM(key)
硬编码密钥 SECRET_KEY = b"..." os_keychain.get_key()
可预测 Nonce struct.pack(">Q", time()) secrets.token_bytes(12)
无认证 modes.CBC(iv) aesgcm.encrypt(nonce, pt, aad)
弱 KDF sha256(password) Argon2id.derive_key()

10. 预实现清单

阶段 1: 编码前

  • [ ] 阅读 references/threat-model.md 中的威胁模型
  • [ ] 识别数据分类(PII、PHI、凭证)
  • [ ] 选择适当算法(AES-256-GCM 或 ChaCha20-Poly1305)
  • [ ] 设计密钥派生策略(Argon2id 参数)
  • [ ] 计划密钥存储(操作系统密钥链集成)
  • [ ] 编写加密/解密往返的失败测试
  • [ ] 编写认证标签验证测试
  • [ ] 编写密钥派生一致性测试

阶段 2: 实现期间

  • [ ] 使用 cryptography 库(非自定义实现)
  • [ ] 使用 secrets.token_bytes(12) 生成 nonce
  • [ ] 实现带 TTL 的密钥缓存以提升性能
  • [ ] 对 >10MB 文件使用流处理
  • [ ] 使用后归零密钥材料(SecureKeyHolder 模式)
  • [ ] 添加关联数据(AAD)用于上下文绑定
  • [ ] 处理 InvalidTag 异常而不泄露信息
  • [ ] 每个函数实现后运行测试

阶段 3: 提交前

  • [ ] 所有 TDD 测试通过,覆盖 90%+
  • [ ] 在 10,000+ 操作上验证 nonce 唯一性
  • [ ] 密钥派生时序差异 <10%
  • [ ] 日志中无秘密(grep -i "key\|secret\|password"
  • [ ] 依赖扫描干净(无 CVE)
  • [ ] 性能基准达到目标:
    • 密钥派生: <1s
    • 加密: >100MB/s
    • 批量操作: 线性扩展
  • [ ] 对高风险代码请求安全审查

11. 总结

关键目标: AES-256-GCM 带随机 nonce、Argon2id KDF、操作系统密钥链集成、认证加密、密钥轮换支持。

安全提醒: 无自定义加密,使用审计过的库,测试认证标签,按计划轮换密钥。

参考: references/advanced-patterns.mdreferences/security-examples.mdreferences/threat-model.md


错误的加密比没有加密更糟——它提供虚假信心。