Angular 2新手求助:C#加密图片后TypeScript解密实现方案
当然可行!不过这里有几个关键细节要注意,比如RSA加密的长度限制、密钥格式的兼容,还有前后端的编码一致性。另外,直接用RSA加密图片这类大文件效率很低,更推荐结合对称加密来做,下面一步步帮你梳理:
一、先理清加密解密的正确逻辑
首先要明确RSA的核心特性:公钥加密,私钥解密(用于保密传输);私钥签名,公钥验签(用于身份验证)。如果你的需求是前端解密图片,那前端需要持有私钥,但私钥暴露在前端会有严重安全风险。因此更合理的方案是:
用AES(对称加密)加密图片(效率高,无长度限制),再用RSA加密AES的密钥,前端拿到加密的AES密钥和加密的图片后,先用RSA解密得到AES密钥,再用AES解密图片。
二、完善你的C#加密代码
下面是完整的C#实现,包含RSA密钥生成、AES密钥生成、图片加密和AES密钥加密:
using System; using System.IO; using System.Security.Cryptography; using System.Xml.Serialization; public class ImageEncryptionHelper { // 生成RSA密钥对(公钥给前端,私钥后端安全存储) public (string PublicKeyXml, string PrivateKeyPkcs8Base64) GenerateRsaKeyPair() { using var rsa = new RSACryptoServiceProvider(2048); // 导出XML格式公钥(方便前端转换) var pubParams = rsa.ExportParameters(false); var pubWriter = new StringWriter(); new XmlSerializer(typeof(RSAParameters)).Serialize(pubWriter, pubParams); string pubKeyXml = pubWriter.ToString(); // 导出PKCS#8格式私钥(Base64编码,方便前端导入Web Crypto) string privKeyPkcs8 = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey()); return (pubKeyXml, privKeyPkcs8); } // 生成AES密钥和IV(128/256位都可以,这里用256位) public (byte[] AesKey, byte[] AesIv) GenerateAesKey() { using var aes = Aes.Create(); aes.KeySize = 256; aes.GenerateKey(); aes.GenerateIV(); return (aes.Key, aes.IV); } // AES加密图片字节 public byte[] EncryptImageWithAes(byte[] imageBytes, byte[] aesKey, byte[] aesIv) { using var aes = Aes.Create(); aes.Key = aesKey; aes.IV = aesIv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write); cs.Write(imageBytes, 0, imageBytes.Length); cs.FlushFinalBlock(); return ms.ToArray(); } // RSA加密AES密钥(用公钥加密,确保只有持有私钥的前端能解密) public byte[] EncryptAesKeyWithRsa(byte[] aesKey, string rsaPublicKeyXml) { using var rsa = new RSACryptoServiceProvider(2048); // 导入XML格式公钥 var pubReader = new StringReader(rsaPublicKeyXml); var pubParams = (RSAParameters)new XmlSerializer(typeof(RSAParameters)).Deserialize(pubReader); rsa.ImportParameters(pubParams); // 用PKCS#1填充(和前端Web Crypto的RSA-PKCS1-v1_5匹配) return rsa.Encrypt(aesKey, false); } }
三、Angular/TypeScript解密实现
前端用浏览器原生的Web Crypto API来解密(无需额外安装库,安全可靠),下面是服务类的实现:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ImageDecryptionService { // 把C#的XML格式公钥转换为Web Crypto支持的格式(如果需要前端加密用,解密的话用私钥) async convertRsaXmlPublicKeyToCryptoKey(xmlPublicKey: string): Promise<CryptoKey> { // 解析XML公钥,提取Modulus和Exponent const modulusMatch = xmlPublicKey.match(/<Modulus>([^<]+)<\/Modulus>/); const exponentMatch = xmlPublicKey.match(/<Exponent>([^<]+)<\/Exponent>/); if (!modulusMatch || !exponentMatch) { throw new Error('Invalid RSA XML public key'); } const modulus = this.base64ToArrayBuffer(modulusMatch[1]); const exponent = this.base64ToArrayBuffer(exponentMatch[1]); // 导入为公钥 return crypto.subtle.importKey( 'spki', await this.spkiFromRsaComponents(modulus, exponent), { name: 'RSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['encrypt'] ); } // 用RSA私钥解密AES密钥 async decryptAesKeyWithRsa(encryptedAesKeyBase64: string, privateKeyPkcs8Base64: string): Promise<CryptoKey> { // 解码Base64的加密AES密钥和PKCS#8私钥 const encryptedAesKey = this.base64ToArrayBuffer(encryptedAesKeyBase64); const privateKeyBytes = this.base64ToArrayBuffer(privateKeyPkcs8Base64); // 导入私钥 const privateKey = await crypto.subtle.importKey( 'pkcs8', privateKeyBytes, { name: 'RSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['decrypt'] ); // 解密AES密钥 const aesKeyBytes = await crypto.subtle.decrypt( { name: 'RSA-PKCS1-v1_5' }, privateKey, encryptedAesKey ); // 导入AES密钥 return crypto.subtle.importKey( 'raw', aesKeyBytes, { name: 'AES-CBC', length: 256 }, false, ['decrypt'] ); } // 用AES解密图片,返回Uint8Array(可直接转为Blob显示) async decryptImageWithAes(encryptedImageBase64: string, aesKey: CryptoKey, ivBase64: string): Promise<Uint8Array> { const encryptedImageBytes = this.base64ToArrayBuffer(encryptedImageBase64); const iv = this.base64ToArrayBuffer(ivBase64); const decryptedBytes = await crypto.subtle.decrypt( { name: 'AES-CBC', iv: iv }, aesKey, encryptedImageBytes ); return new Uint8Array(decryptedBytes); } // 工具方法:Base64转ArrayBuffer private base64ToArrayBuffer(base64: string): ArrayBuffer { const binaryString = window.atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } // 工具方法:从RSA组件生成SPKI格式公钥 private async spkiFromRsaComponents(modulus: ArrayBuffer, exponent: ArrayBuffer): Promise<ArrayBuffer> { // 构建ASN.1结构,这里推荐用`jsrsasign`库简化转换,避免手动写复杂逻辑 // 示例:可以安装jsrsasign后用以下代码 // const rsa = new RSAKey(); // rsa.setPublic(this.arrayBufferToBase64(modulus), this.arrayBufferToBase64(exponent)); // const spki = rsa.getPublicKeySPKI(); // return this.base64ToArrayBuffer(spki); throw new Error('Implement SPKI conversion or install jsrsasign library'); } // 工具方法:ArrayBuffer转Base64 private arrayBufferToBase64(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } }
四、关键注意事项
- 密钥安全:绝对不要把RSA私钥明文传输或存储在前端代码中!可以用浏览器的
crypto.subtle.exportKey结合IndexedDB等安全存储机制来保存私钥。 - 算法匹配:前后端的加密算法、填充方式、哈希算法必须完全一致!比如C#用
RSA-PKCS1-v1_5填充,前端也要对应相同的算法配置。 - 编码一致性:后端把加密后的字节转成Base64字符串传给前端,前端再转成
Uint8Array处理,避免出现编码混乱。 - 大文件处理:如果图片体积很大,建议后端分段加密,前端分段解密后再拼接,避免内存溢出。
内容的提问来源于stack exchange,提问作者Navya Sri
相关产品推荐
相关产品推荐

