Hur man verifierar en ECDSA-signatur med hjälp av Web Crypto
Av Mick Hansen den 22 januari 2026
2 min lästid

Om du har försökt implementera WebAuthn via webbläsarens API för Credential Management kan du ha haft problem med att verifiera en ES256-signatur med hjälp av Web Crypto eller node crypto.
ES256 är en av de algoritmer som rekommenderas av specifikationen för Web Authentication. Om du följer den rekommenderade ordningen måste du verifiera en ECDSA-signatur (när enheter stöder den).
Du kanske har hanterat alla WebAuthn-"konstigheter":
- Konverterat den COSE-kodade offentliga nyckeln till ett format som stöds av Web Crypto (t.ex. JWK)
- Genererat rätt indata genom att kombinera byte authenticatorData och hash av clientDataJSON
... och fortfarande misslyckas signaturen med att verifiera.
Varför inte?
Kärnan är att Web Crypto förväntar sig att ECDSA-signaturer ska tillhandahållas i r|s-format, medan WebAuthn, enligt specifikationen, producerar en ASN.1 DER.
Låt oss gå igenom de få steg som krävs för att konvertera och verifiera en ASN.1-formaterad signatur.
Några verktygsmetoder
När vi arbetar med binära buffertar behöver vi en hjälpmetod för att slå samman två buffertar:
function mergeBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer) {
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
Vi behöver också ett sätt att avkoda ASN.1 DER ECDSA-Sig-Value, som helt enkelt är en ASN.1-sekvens som innehåller två heltal:
function readAsn1IntegerSequence(input: Uint8Array) {
if (input[0] !== 0x30) throw new Error('Input is not an ASN.1 sequence');
const seqLength = input[1];
const elements : Uint8Array[] = [];
let current = input.slice(2, 2 + seqLength);
while (current.length > 0) {
const tag = current[0];
if (tag !== 0x02) throw new Error('Expected ASN.1 sequence element to be an INTEGER');
const elLength = current[1];
elements.push(current.slice(2, 2 + elLength));
current = current.slice(2 + elLength);
}
return elements;
}
Redovisning av binär formatering
ASN.1 DER och r|s använder subtilt olika utfyllnader och binära kodningstekniker, så vi måste utföra vissa kontroller och modifieringar av bytearrays.
Följande kod är anpassad från https://github.com/kjur/jsrsasign/blob/58bb24192f501927014b67911bbde8ef27532319/src/ecdsa-modified-1.0.js#L760 för att fungera med binära matriser istället för hexsträngar.
function convertEcdsaAsn1Signature(input : Uint8Array) {
const elements = readAsn1IntegerSequence(input);
if (elements.length !== 2) throw new Error('Expected 2 ASN.1 sequence elements');
let [r, s] = elements;
// R and S length is assumed multiple of 128bit.
// If leading is 0 and modulo of length is 1 byte then
// leading 0 is for two's complement and will be removed.
if (r[0] === 0 && r.byteLength % 16 == 1) {
r = r.slice(1);
}
if (s[0] === 0 && s.byteLength % 16 == 1) {
s = s.slice(1);
}
// R and S length is assumed multiple of 128bit.
// If missing a byte then it will be padded by 0.
if ((r.byteLength % 16) == 15) {
r = new Uint8Array(mergeBuffer(new Uint8Array([0]), r));
}
if ((s.byteLength % 16) == 15) {
s = new Uint8Array(mergeBuffer(new Uint8Array([0]), s));
}
// If R and S length is not still multiple of 128bit,
// then error
if (r.byteLength % 16 != 0) throw Error("unknown ECDSA sig r length error");
if (s.byteLength % 16 != 0) throw Error("unknown ECDSA sig s length error");
return mergeBuffer(r, s);
}
Att sätta ihop allt
Nu har vi alla bitar som krävs för att konvertera och verifiera en WebAuthn ECDSA-signatur med hjälp av Web Crypto (förutom att konvertera en COSE-kodad offentlig nyckel, men det kommer vi att täcka i en framtida artikel).
const key : CryptoKey = ...;
const response : AuthenticatorAssertionResponse = ...;
const hashedClientDataJSON = await globalThis.crypto.subtle.digest('SHA-256', response.clientDataJSON);
const data = mergeBuffer(response.authenticatorData, hashedClientDataJSON);
const signature = convertEcdsaAsn1Signature(new Uint8Array(response.signature));
const verified = await globalThis.crypto.subtle.verify({
name: 'ECDSA',
hash: 'ES256'
}, key, signature, data);
Idura utvecklar produkter för digital identitet och signaturer med hjälp av WebAuthn.
Dessa relaterade artiklar

Varför digitala signaturer inte ger integritet

Fördelarna med BankID-signaturer för företag

Introduktion till kryptografi: Vad är hashing?
Sign up for our newsletter
Stay up to date on industry news and insights