Update 1 – posting the code to test the solution.
Given seed and salt how do I produce a set of deterministic ECDSA
keys using zero-dependencies JavaScript
and SubtleCrypto
?
This is a continuation of this question, which I was advised to form as a new question. Therefore I publish no sample code, my understanding is that there’re 2 major ways to go about it:
- Generate the key material and then somehow import it as
ECDSA
keys. - Generate
x
&y
required to generateECDSA
keys (I’m not sure how, given thegenerateKey
call accepting just named curve, maybe viajwk
(?))
I’d like the solution to rely as much as possibly on SubtleCrypto
API with as little custom code as possible.
The code:
<code>async function generateDeterministicECDSAKey(seed, salt) {
// Step 1: Convert seed and salt to ArrayBuffers
const enc = new TextEncoder();
const seedBuffer = enc.encode(seed); // Seed as a buffer
const saltBuffer = enc.encode(salt); // Salt as a buffer
// Step 2: Derive key material using PBKDF2
const baseKey = await crypto.subtle.importKey(
'raw',
seedBuffer,
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
// Derive 256 bits (32 bytes) of key material for the private key
const derivedBits = await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltBuffer, // Salt for PBKDF2
iterations: 100000, // Use a secure iteration count
hash: 'SHA-256' // Hash function for PBKDF2
},
baseKey,
256 // 256 bits for the derived key
);
// Step 3: Import derived key material as ECDSA private key
// NOTE - that's where it fails due to a mismatch between the "raw" format
// and { name: 'ECDSA', namedCurve: 'P-256' } algo
const privateKey = await crypto.subtle.importKey(
'raw',
derivedBits, // Use derived bits as private key
{ name: 'ECDSA', namedCurve: 'P-256' }, // ECDSA using the P-256 curve
true, // Mark key as exportable
['sign'] // Key usage for signing
);
// Step 4: Export the private key in PKCS8 format (optional)
const pkcs8Key = await crypto.subtle.exportKey('pkcs8', privateKey);
// Step 5: Generate public key pair (optional)
const publicKey = await crypto.subtle.exportKey('jwk', privateKey);
return {
privateKey: privateKey,
pkcs8Key: pkcs8Key,
publicKey: publicKey
};
}
// Helper function to convert ArrayBuffer to base64 string (for display)
function arrayBufferToBase64(buffer) {
const binary = String.fromCharCode.apply(null, new Uint8Array(buffer));
return window.btoa(binary);
}
// Example usage
const seed = "your_deterministic_seed"; // Example seed
const salt = "your_salt_value"; // Example salt
await generateDeterministicECDSAKey(seed, salt).then(keys => {
const privateKeyBase64 = arrayBufferToBase64(keys.pkcs8Key);
console.log("Private Key (PKCS8 Base64):", privateKeyBase64);
console.log("Public Key (JWK):", keys.publicKey);
});
</code>
<code>async function generateDeterministicECDSAKey(seed, salt) {
// Step 1: Convert seed and salt to ArrayBuffers
const enc = new TextEncoder();
const seedBuffer = enc.encode(seed); // Seed as a buffer
const saltBuffer = enc.encode(salt); // Salt as a buffer
// Step 2: Derive key material using PBKDF2
const baseKey = await crypto.subtle.importKey(
'raw',
seedBuffer,
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
// Derive 256 bits (32 bytes) of key material for the private key
const derivedBits = await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltBuffer, // Salt for PBKDF2
iterations: 100000, // Use a secure iteration count
hash: 'SHA-256' // Hash function for PBKDF2
},
baseKey,
256 // 256 bits for the derived key
);
// Step 3: Import derived key material as ECDSA private key
// NOTE - that's where it fails due to a mismatch between the "raw" format
// and { name: 'ECDSA', namedCurve: 'P-256' } algo
const privateKey = await crypto.subtle.importKey(
'raw',
derivedBits, // Use derived bits as private key
{ name: 'ECDSA', namedCurve: 'P-256' }, // ECDSA using the P-256 curve
true, // Mark key as exportable
['sign'] // Key usage for signing
);
// Step 4: Export the private key in PKCS8 format (optional)
const pkcs8Key = await crypto.subtle.exportKey('pkcs8', privateKey);
// Step 5: Generate public key pair (optional)
const publicKey = await crypto.subtle.exportKey('jwk', privateKey);
return {
privateKey: privateKey,
pkcs8Key: pkcs8Key,
publicKey: publicKey
};
}
// Helper function to convert ArrayBuffer to base64 string (for display)
function arrayBufferToBase64(buffer) {
const binary = String.fromCharCode.apply(null, new Uint8Array(buffer));
return window.btoa(binary);
}
// Example usage
const seed = "your_deterministic_seed"; // Example seed
const salt = "your_salt_value"; // Example salt
await generateDeterministicECDSAKey(seed, salt).then(keys => {
const privateKeyBase64 = arrayBufferToBase64(keys.pkcs8Key);
console.log("Private Key (PKCS8 Base64):", privateKeyBase64);
console.log("Public Key (JWK):", keys.publicKey);
});
</code>
async function generateDeterministicECDSAKey(seed, salt) {
// Step 1: Convert seed and salt to ArrayBuffers
const enc = new TextEncoder();
const seedBuffer = enc.encode(seed); // Seed as a buffer
const saltBuffer = enc.encode(salt); // Salt as a buffer
// Step 2: Derive key material using PBKDF2
const baseKey = await crypto.subtle.importKey(
'raw',
seedBuffer,
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
// Derive 256 bits (32 bytes) of key material for the private key
const derivedBits = await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltBuffer, // Salt for PBKDF2
iterations: 100000, // Use a secure iteration count
hash: 'SHA-256' // Hash function for PBKDF2
},
baseKey,
256 // 256 bits for the derived key
);
// Step 3: Import derived key material as ECDSA private key
// NOTE - that's where it fails due to a mismatch between the "raw" format
// and { name: 'ECDSA', namedCurve: 'P-256' } algo
const privateKey = await crypto.subtle.importKey(
'raw',
derivedBits, // Use derived bits as private key
{ name: 'ECDSA', namedCurve: 'P-256' }, // ECDSA using the P-256 curve
true, // Mark key as exportable
['sign'] // Key usage for signing
);
// Step 4: Export the private key in PKCS8 format (optional)
const pkcs8Key = await crypto.subtle.exportKey('pkcs8', privateKey);
// Step 5: Generate public key pair (optional)
const publicKey = await crypto.subtle.exportKey('jwk', privateKey);
return {
privateKey: privateKey,
pkcs8Key: pkcs8Key,
publicKey: publicKey
};
}
// Helper function to convert ArrayBuffer to base64 string (for display)
function arrayBufferToBase64(buffer) {
const binary = String.fromCharCode.apply(null, new Uint8Array(buffer));
return window.btoa(binary);
}
// Example usage
const seed = "your_deterministic_seed"; // Example seed
const salt = "your_salt_value"; // Example salt
await generateDeterministicECDSAKey(seed, salt).then(keys => {
const privateKeyBase64 = arrayBufferToBase64(keys.pkcs8Key);
console.log("Private Key (PKCS8 Base64):", privateKeyBase64);
console.log("Public Key (JWK):", keys.publicKey);
});
6