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

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.Pkcs1 in C# and RSASSA-PKCS1-v1_5 in JS—this is the default for RSACryptoServiceProvider, 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:21:28