Windows 10下C#与JS间RSA SHA256签名双向验证实现问询
Alright, let's tackle both of your scenarios step by step—both are straightforward using standard APIs, no third-party libraries required if you're working with modern browsers or Node.js. I'll tie everything back to how it integrates with C#'s RSACryptoServiceProvider since that's your reference point.
1. Verifying a C#-Generated RSA-SHA256 Signature in JavaScript
First, let's align on what your C# signing code might look like (since you mentioned you already have this, it's just to confirm format consistency):
using System.Security.Cryptography; using System.Text; public static string SignData(string data, RSACryptoServiceProvider rsa) { byte[] dataBytes = Encoding.UTF8.GetBytes(data); // Uses PKCS#1 v1.5 padding (RSACryptoServiceProvider's default) and SHA256 byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return Convert.ToBase64String(signatureBytes); } // Export public key in SPKI format (Base64) for JavaScript to use public static string ExportPublicKey(RSACryptoServiceProvider rsa) { return Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); }
Browser Implementation (Web Crypto API)
Modern browsers support the Web Crypto API natively for this. You'll need the SPKI-formatted public key from C# and the Base64 signature:
async function verifySignature(data, signatureBase64, publicKeyBase64) { // Convert Base64 public key to ArrayBuffer const publicKeyBuffer = Uint8Array.from(atob(publicKeyBase64), c => c.charCodeAt(0)); // Import the public key for verification const publicKey = await crypto.subtle.importKey( "spki", publicKeyBuffer, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, // Key is not extractable ["verify"] // Only allow verification ); // Prepare data and signature as ArrayBuffers const dataBuffer = new TextEncoder().encode(data); const signatureBuffer = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0)); // Run verification return crypto.subtle.verify( "RSASSA-PKCS1-v1_5", publicKey, signatureBuffer, dataBuffer ); } // Usage example const data = "Hello from C#"; const csharpSignature = "YOUR_BASE64_SIGNATURE_FROM_C#"; const csharpPublicKey = "YOUR_SPKI_BASE64_PUBLIC_KEY_FROM_C#"; verifySignature(data, csharpSignature, csharpPublicKey).then(isValid => { console.log(`Signature valid? ${isValid}`); });
Node.js Implementation (Built-in Crypto Module)
Node.js has its own crypto module that works similarly. You'll just need to convert the SPKI Base64 to a PEM format first:
const crypto = require('crypto'); function verifySignature(data, signatureBase64, publicKeyBase64) { // Convert SPKI Base64 to PEM format const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${publicKeyBase64.match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`; // Verify the signature return crypto.verify( 'sha256', Buffer.from(data, 'utf8'), { key: publicKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING }, Buffer.from(signatureBase64, 'base64') ); } // Usage example const data = "Hello from C#"; const csharpSignature = "YOUR_BASE64_SIGNATURE_FROM_C#"; const csharpPublicKey = "YOUR_SPKI_BASE64_PUBLIC_KEY_FROM_C#"; const isValid = verifySignature(data, csharpSignature, csharpPublicKey); console.log(`Signature valid? ${isValid}`);
2. Generating an RSA-SHA256 Signature in JavaScript, Verifying in C#
This is the reverse flow—we'll generate a signature in JS (either with a new key pair or your Windows certificate's private key) and verify it with RSACryptoServiceProvider.
Browser Implementation (Web Crypto API)
Option 1: Generate a New Key Pair
If you don't need to use your existing Windows certificate, generate a new key pair directly in the browser:
async function generateSignature(data) { // Generate RSA key pair (matches C#'s default settings: 2048 bits, 65537 exponent) const keyPair = await crypto.subtle.generateKey( { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 hash: "SHA-256" }, true, // Allow key extraction for exporting ["sign", "verify"] ); // Export public key in SPKI format (Base64) for C# const publicKeyBuffer = await crypto.subtle.exportKey("spki", keyPair.publicKey); const publicKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer))); // Generate signature const dataBuffer = new TextEncoder().encode(data); const signatureBuffer = await crypto.subtle.sign( "RSASSA-PKCS1-v1_5", keyPair.privateKey, dataBuffer ); const signatureBase64 = btoa(String.fromCharCode(...new Uint8Array(signatureBuffer))); return { signature: signatureBase64, publicKey: publicKeyBase64 }; } // Usage example generateSignature("Hello from JS").then(result => { console.log("JS Signature:", result.signature); console.log("Public Key for C#:", result.publicKey); });
Option 2: Use Your Windows Certificate's Private Key
If you want to sign with the X509 certificate in your Windows user store, browsers can access this via Web Crypto (requires user consent):
async function signWithWindowsCertificate(data) { // Request user to select a certificate for signing const keys = await window.crypto.subtle.selectKey({ keyType: "private", keyUsages: ["sign"], certificate: { // Optional: Filter certificates by subject, issuer, etc. // subject: "CN=Your Certificate Subject" } }); const privateKey = keys[0]; // Generate signature const dataBuffer = new TextEncoder().encode(data); const signatureBuffer = await crypto.subtle.sign( { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, privateKey, dataBuffer ); const signatureBase64 = btoa(String.fromCharCode(...new Uint8Array(signatureBuffer))); // Export public key for C# const publicKeyBuffer = await crypto.subtle.exportKey("spki", privateKey.publicKey); const publicKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer))); return { signature: signatureBase64, publicKey: publicKeyBase64 }; }
Node.js Implementation
Generate a key pair and sign directly with Node.js's crypto module:
const crypto = require('crypto'); function generateSignature(data) { // Generate RSA key pair const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicExponent: 0x10001, // 65537 publicKeyEncoding: { type: 'spki', format: 'base64' }, privateKeyEncoding: { type: 'pkcs8', format: 'base64' } }); // Convert private key to PEM format for signing const privateKeyPem = `-----BEGIN PRIVATE KEY-----\n${privateKey.match(/.{1,64}/g).join('\n')}\n-----END PRIVATE KEY-----`; // Generate signature const signature = crypto.sign( 'sha256', Buffer.from(data, 'utf8'), { key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING } ).toString('base64'); return { signature, publicKey }; } // Usage example const result = generateSignature("Hello from JS"); console.log("JS Signature:", result.signature); console.log("Public Key for C#:", result.publicKey);
C# Verification Code
Regardless of whether the signature came from a browser or Node.js, the C# verification code is the same. Use the SPKI-formatted public key from JS:
using System.Security.Cryptography; using System.Text; public static bool VerifyJavaScriptSignature(string data, string jsSignatureBase64, string jsPublicKeyBase64) { byte[] dataBytes = Encoding.UTF8.GetBytes(data); byte[] signatureBytes = Convert.FromBase64String(jsSignatureBase64); byte[] publicKeyBytes = Convert.FromBase64String(jsPublicKeyBase64); using (var rsa = new RSACryptoServiceProvider()) { // Import the SPKI public key from JS rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _); // Verify using PKCS#1 v1.5 padding and SHA256 return rsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } } // Usage example string data = "Hello from JS"; string jsSignature = "YOUR_BASE64_SIGNATURE_FROM_JS"; string jsPublicKey = "YOUR_SPKI_BASE64_PUBLIC_KEY_FROM_JS"; bool isValid = VerifyJavaScriptSignature(data, jsSignature, jsPublicKey); Console.WriteLine($"Signature valid? {isValid}");
Key Notes to Avoid Pitfalls
- Consistent Padding: Always use
RSASignaturePadding.Pkcs1in C# andRSASSA-PKCS1-v1_5in JS—this is the default forRSACryptoServiceProvider, so don't switch to OAEP unless you explicitly configure both sides to use it. - Encoding: Stick to UTF-8 for string-to-byte conversions in both JS and C#.
- Key Length: Ensure your RSA keys are the same length (2048 bits is standard, 4096 is more secure but slower).
- Base64 Handling: Make sure there are no extra whitespace or newline characters in your Base64 strings when passing between JS and C#.
内容的提问来源于stack exchange,提问作者Anatoliy Tkachenko

