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

C#加密PHI/PII数据,无CryptoJS的JavaScript解密方案咨询

针对你的场景——C#后端加密PHI/PII,前端JS解密,不能用CryptoJS,还要在SSL之上再加一层加密,我整理了几个基于原生库的可行方案,兼顾安全性和实用性:

方案1:AES-GCM混合加密(首推,安全+高性能)

AES-GCM是带认证的对称加密算法,既能保证数据机密性,又能防止篡改,性能也比非对称加密高很多。核心思路是:后端用AES加密数据,同时用RSA加密AES密钥(避免密钥明文传输),前端解密AES密钥后再解密数据。

C#后端加密代码

using System;
using System.Security.Cryptography;
using System.Text;

public class SecureDataEncryption
{
    // 生成256位AES密钥(安全随机)
    public static byte[] GenerateAesKey() => RandomNumberGenerator.GetBytes(32);

    // AES-GCM加密数据
    public static (string CipherText, string Iv, string AuthTag) EncryptWithAesGcm(string plainText, byte[] aesKey)
    {
        using var aesGcm = new AesGcm(aesKey);
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var iv = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize); // GCM的IV(Nonce)
        var cipherBytes = new byte[plainBytes.Length];
        var authTag = new byte[AesGcm.TagByteSizes.MaxSize]; // 认证标签,用于验证数据完整性

        aesGcm.Encrypt(iv, plainBytes, cipherBytes, authTag);

        return (
            Convert.ToBase64String(cipherBytes),
            Convert.ToBase64String(iv),
            Convert.ToBase64String(authTag)
        );
    }

    // 用RSA公钥加密AES密钥(公钥由前端生成并传给后端)
    public static string EncryptAesKeyWithRsa(byte[] aesKey, string rsaPublicKeyBase64)
    {
        var publicKeyBytes = Convert.FromBase64String(rsaPublicKeyBase64);
        using var rsa = RSA.Create();
        rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);

        // 使用OAEP填充,比PKCS#1更安全
        var encryptedKey = rsa.Encrypt(aesKey, RSAEncryptionPadding.OaepSHA256);
        return Convert.ToBase64String(encryptedKey);
    }
}

前端JS解密代码(用原生Web Crypto API)

// 前端生成RSA密钥对(只在需要时生成,私钥不导出、不传输)
async function generateRsaKeyPair() {
    return crypto.subtle.generateKey(
        { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
        false, // 私钥不可提取,更安全
        ["encrypt", "decrypt"]
    );
}

// 导出公钥给后端
async function exportRsaPublicKey(keyPair) {
    const publicKeyBytes = await crypto.subtle.exportKey("spki", keyPair.publicKey);
    return btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes)));
}

// 解密RSA加密的AES密钥
async function decryptAesKeyWithRsa(encryptedAesKeyBase64, privateKey) {
    const encryptedKey = Uint8Array.from(atob(encryptedAesKeyBase64), c => c.charCodeAt(0));
    return crypto.subtle.decrypt(
        { name: "RSA-OAEP" },
        privateKey,
        encryptedKey
    );
}

// AES-GCM解密数据
async function decryptAesGcm(cipherTextBase64, ivBase64, authTagBase64, aesKey) {
    const cipherText = Uint8Array.from(atob(cipherTextBase64), c => c.charCodeAt(0));
    const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0));
    const authTag = Uint8Array.from(atob(authTagBase64), c => c.charCodeAt(0));

    const cryptoKey = await crypto.subtle.importKey(
        "raw",
        aesKey,
        { name: "AES-GCM" },
        false,
        ["decrypt"]
    );

    const decryptedBytes = await crypto.subtle.decrypt(
        { name: "AES-GCM", iv: iv, tag: authTag },
        cryptoKey,
        cipherText
    );

    return new TextDecoder().decode(decryptedBytes);
}

// 完整解密流程示例
async function decryptPhiData(encryptedData) {
    // 假设前端已经生成并保存了RSA密钥对,这里从内存中获取
    const rsaKeyPair = await generateRsaKeyPair();
    // 解密AES密钥
    const aesKey = await decryptAesKeyWithRsa(encryptedData.EncryptedAesKey, rsaKeyPair.privateKey);
    // 解密PHI数据
    return decryptAesGcm(encryptedData.CipherText, encryptedData.Iv, encryptedData.AuthTag, aesKey);
}
方案2:RSA直接加密(仅适合小体量数据)

如果你的PHI/PII是小字段(比如身份证号、手机号,单条数据小于190字节左右,取决于RSA密钥长度),可以直接用RSA加密。但注意RSA性能低,不适合大体积数据。

C#后端加密(用前端传的公钥)

public static string EncryptSmallDataWithRsa(string plainText, string rsaPublicKeyBase64)
{
    var publicKeyBytes = Convert.FromBase64String(rsaPublicKeyBase64);
    using var rsa = RSA.Create();
    rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);

    var plainBytes = Encoding.UTF8.GetBytes(plainText);
    var cipherBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
    return Convert.ToBase64String(cipherBytes);
}

前端JS解密

async function decryptSmallRsaData(encryptedDataBase64, privateKey) {
    const encryptedBytes = Uint8Array.from(atob(encryptedDataBase64), c => c.charCodeAt(0));
    const decryptedBytes = await crypto.subtle.decrypt(
        { name: "RSA-OAEP" },
        privateKey,
        encryptedBytes
    );
    return new TextDecoder().decode(decryptedBytes);
}
方案3:PBKDF2密钥派生(基于用户密码的场景)

如果你的系统有用户登录密码,可以用PBKDF2从密码派生相同的密钥,两端用相同的盐、迭代次数和哈希算法。适合需要用户密码才能解密数据的场景。

C#后端加密

public static (string CipherText, string Iv, string AuthTag, string Salt) EncryptWithPassword(string plainText, string password)
{
    var salt = RandomNumberGenerator.GetBytes(16); // 随机盐,每次加密都不同
    using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256);
    var aesKey = pbkdf2.GetBytes(32);

    var (cipherText, iv, authTag) = EncryptWithAesGcm(plainText, aesKey);
    return (cipherText, iv, authTag, Convert.ToBase64String(salt));
}

前端JS解密

async function deriveKeyFromPassword(password, saltBase64) {
    const salt = Uint8Array.from(atob(saltBase64), c => c.charCodeAt(0));
    const passwordBytes = new TextEncoder().encode(password);

    const keyMaterial = await crypto.subtle.importKey(
        "raw",
        passwordBytes,
        { name: "PBKDF2" },
        false,
        ["deriveKey"]
    );

    return crypto.subtle.deriveKey(
        { name: "PBKDF2", salt: salt, iterations: 100000, hash: "SHA-256" },
        keyMaterial,
        { name: "AES-GCM", length: 256 },
        false,
        ["decrypt"]
    );
}

async function decryptWithPassword(cipherTextBase64, ivBase64, authTagBase64, saltBase64, password) {
    const key = await deriveKeyFromPassword(password, saltBase64);
    const cipherText = Uint8Array.from(atob(cipherTextBase64), c => c.charCodeAt(0));
    const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0));
    const authTag = Uint8Array.from(atob(authTagBase64), c => c.charCodeAt(0));

    const decryptedBytes = await crypto.subtle.decrypt(
        { name: "AES-GCM", iv: iv, tag: authTag },
        key,
        cipherText
    );

    return new TextDecoder().decode(decryptedBytes);
}
关键安全注意事项
  • 绝对不要硬编码密钥:C#后端用环境变量或安全密钥管理服务存储密钥;前端不要将敏感密钥存在本地存储(用内存暂存即可)。
  • 优先用AES-GCM:避免使用CBC模式,因为CBC需要额外的padding,容易引发padding oracle攻击,而GCM自带认证和完整性校验。
  • RSA密钥对管理:前端生成的RSA私钥设置为不可提取(extractable: false),防止被恶意脚本窃取。
  • 编码一致性:两端都用UTF-8编码字符串,Base64传输字节数据,避免乱码。

内容的提问来源于stack exchange,提问作者Dean.DePue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:33:01