Skip to content

aghiles-ait/ecies-reencryption

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ECIES with RSA-Wrapped Shared Secret

This library implements ECIES (Elliptic Curve Integrated Encryption Scheme) with an additional RSA layer that wraps the shared secret before decryption.

ecies_decrypt_with_rsa() - JavaScript Implementation Specs

Function Signature

function eciesDecryptWithRsa(
  encryptedSharedSecret: Uint8Array,  // RSA-encrypted EC point (256 bytes for 2048-bit RSA)
  rsaPrivateKey: CryptoKey,           // RSA private key
  ciphertext: Uint8Array,             // AES-GCM encrypted data (includes 16-byte auth tag)
  nonce: Uint8Array,                  // 12 bytes (96-bit)
  context: Uint8Array                 // Arbitrary bytes, used as HKDF info
): Promise<Uint8Array>                // Decrypted plaintext

Step-by-Step Algorithm

Step 1: RSA Decrypt the Shared Secret

Property Value
Algorithm RSA-OAEP
Hash SHA-256
Input encryptedSharedSecret (256 bytes for 2048-bit key)
Output 33 bytes (compressed SEC1 EC point)
const pointBytes = await crypto.subtle.decrypt(
  { name: "RSA-OAEP" },
  rsaPrivateKey,
  encryptedSharedSecret
);
// Result: 33 bytes - compressed secp256k1 point

Step 2: Decode the EC Point and Extract X-Coordinate

Property Value
Curve secp256k1 (k256)
Point Format Compressed SEC1
Point Size 33 bytes
Format 0x02 or 0x03 prefix + 32 bytes X-coordinate
// pointBytes format:
// - Byte 0: 0x02 (even Y) or 0x03 (odd Y)
// - Bytes 1-32: X-coordinate (32 bytes, big-endian)

const xCoordinate = pointBytes.slice(1, 33); // 32 bytes

Note: You only need the X-coordinate for key derivation. No need to decompress the full point.


Step 3: Derive AES Key using HKDF

Property Value
Algorithm HKDF
Hash SHA-256
Salt Empty/None (new Uint8Array(0))
IKM (Input Key Material) X-coordinate (32 bytes)
Info context parameter
Output Length 32 bytes (256 bits)
// Import the X-coordinate as raw key material
const ikm = await crypto.subtle.importKey(
  "raw",
  xCoordinate,
  "HKDF",
  false,
  ["deriveKey"]
);

// Derive the AES-256 key
const aesKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    hash: "SHA-256",
    salt: new Uint8Array(0),  // No salt
    info: context              // The context bytes
  },
  ikm,
  { name: "AES-GCM", length: 256 },
  false,
  ["decrypt"]
);

Step 4: AES-GCM Decrypt

Property Value
Algorithm AES-256-GCM
Key Size 256 bits (32 bytes)
Nonce/IV Size 96 bits (12 bytes)
Tag Size 128 bits (16 bytes, appended to ciphertext)
const plaintext = await crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv: nonce,
    tagLength: 128  // 16 bytes
  },
  aesKey,
  ciphertext  // Includes auth tag at the end
);

Complete JavaScript Implementation

async function eciesDecryptWithRsa(
  encryptedSharedSecret,
  rsaPrivateKey,
  ciphertext,
  nonce,
  context
) {
  // Step 1: RSA-OAEP decrypt to recover the EC point
  const pointBytes = new Uint8Array(
    await crypto.subtle.decrypt(
      { name: "RSA-OAEP" },
      rsaPrivateKey,
      encryptedSharedSecret
    )
  );

  // Step 2: Extract X-coordinate (bytes 1-32 of compressed point)
  const xCoordinate = pointBytes.slice(1, 33);

  // Step 3: HKDF to derive AES key
  const ikm = await crypto.subtle.importKey(
    "raw",
    xCoordinate,
    "HKDF",
    false,
    ["deriveKey"]
  );

  const aesKey = await crypto.subtle.deriveKey(
    {
      name: "HKDF",
      hash: "SHA-256",
      salt: new Uint8Array(0),
      info: context
    },
    ikm,
    { name: "AES-GCM", length: 256 },
    false,
    ["decrypt"]
  );

  // Step 4: AES-GCM decrypt
  const plaintext = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv: nonce, tagLength: 128 },
    aesKey,
    ciphertext
  );

  return new Uint8Array(plaintext);
}

RSA Key Requirements

When importing or generating the RSA private key:

// For importing a PKCS#8 private key:
const rsaPrivateKey = await crypto.subtle.importKey(
  "pkcs8",
  privateKeyDer,
  {
    name: "RSA-OAEP",
    hash: "SHA-256"  // Must match!
  },
  false,
  ["decrypt"]
);

Test Vectors

To verify compatibility, have the Rust side output these intermediate values:

Value Description
encrypted_shared_secret Hex-encoded RSA ciphertext
point_bytes (after RSA decrypt) 33 bytes, hex-encoded
x_coordinate 32 bytes, hex-encoded
derived_key (after HKDF) 32 bytes, hex-encoded
nonce 12 bytes
context The info string
ciphertext AES-GCM output
plaintext Expected result

Summary Table

Component Algorithm Parameters
RSA Decryption RSA-OAEP SHA-256, 2048+ bit key
EC Point secp256k1 Compressed SEC1 (33 bytes)
Key Derivation HKDF-SHA256 salt=empty, info=context, output=32 bytes
Symmetric Encryption AES-256-GCM nonce=12 bytes, tag=16 bytes

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages