首页 > 解决方案 > JavaScript WebCrypto API unwrapKey

问题描述

我正在使用 JavaScript WebCrypto API。我需要做下一个序列:

  1. 生成 RSA-PSS 密钥对;
  2. 使用 AES-GCM 封装私钥;
  3. 有机会解开第 2 步的结果。

我知道我必须从 AES-GCM 存储盐和 InitialVector。但是,当我尝试解开包装(ArrayBuffer)的结果时,它失败了:

DataError - Data provided to an operation does not meet requirements. 

我试图将一个空的传递ArrayBufferunwrapKey但它失败了OperationError。我也尝试将结果wrapKey直接传递给unwrapKey(而不转换为UInt8Array),但结果是一样的。

我使用相同的盐、IV 和密钥材料来导出加密/解密密钥。

我不能说什么是错的。

我的代码:

var salt;
var iv;

    window.onload = function() {
        salt = window.crypto.getRandomValues(new Uint8Array(16));
        iv = window.crypto.getRandomValues(new Uint8Array(12));
    };

/*This function was copied from Mozilla's example*/
    function bytesToArrayBuffer(bytes) {
      const bytesAsArrayBuffer = new ArrayBuffer(bytes.length);
      const bytesUint8 = new Uint8Array(bytesAsArrayBuffer);
      bytesUint8.set(bytes);
      return bytesAsArrayBuffer;
    }

/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
function getKeyMaterial() {
  const password = "123qweasd"; 
  const enc = new TextEncoder();
  return window.crypto.subtle.importKey(
    "raw",
    enc.encode(password),
    {name: "PBKDF2"},
    false,
    ["deriveBits", "deriveKey"]
  );
}

/*
Given some key material and some random salt
derive an AES-GCM key using PBKDF2.
*/
function getKey(keyMaterial, salt) {
  return window.crypto.subtle.deriveKey(
    {
      "name": "PBKDF2",
      salt: salt,
      "iterations": 100000,
      "hash": "SHA-256"
    },
    keyMaterial,
    { "name": "AES-GCM", "length": 256},
    true,
    [ "wrapKey", "unwrapKey" ]
  );
}

/*
Wrap the given key.
*/
async function wrapCryptoKey(keyToWrap) {
  // get the key encryption key
  const keyMaterial = await getKeyMaterial();
  const wrappingKey = await getKey(keyMaterial, salt);
  return window.crypto.subtle.wrapKey(
    "pkcs8",
    keyToWrap,
    wrappingKey,
    {
      name: "AES-GCM",
      iv: iv
    }
  );

}

/*
Derive an AES-GCM key using PBKDF2.
*/
async function getUnwrappingKey() {
  // 1. get the key material (user-supplied password)
  const keyMaterial = await getKeyMaterial();
  return window.crypto.subtle.deriveKey(
    {
      "name": "PBKDF2",
      salt: salt, 
      "iterations": 100000,
      "hash": "SHA-256"
    },
    keyMaterial,
    { "name": "AES-GCM", "length": 256},
    true,
    [ "wrapKey", "unwrapKey" ]
  );
}

async function unwrapPrivateKey(wrappedKeyPar) {
  // 1. get the unwrapping key
  const unwrappingKey = await getUnwrappingKey();
  console.log('Got unwrapping key');
  // 2. initialize the wrapped key
  console.log('Param: '+wrappedKeyPar);
  var wrappedKeyBuffer = bytesToArrayBuffer(wrappedKeyPar); 
  console.log('BufferLength: '+wrappedKeyBuffer.byteLength);

  // 3. initialize the iv
  const ivBuffer = iv; 
  // 4. unwrap the key
  console.log('BEFORE!');

  const unwrappedPKey = await window.crypto.subtle.unwrapKey(
    "pkcs8",               // import format
    wrappedKeyBuffer,      // ArrayBuffer representing key to unwrap
    unwrappingKey,         // CryptoKey representing key encryption key
    {                      // algorithm params for key encryption key
      name: "AES-GCM",
      iv: iv
    },
    {                      // algorithm params for key to unwrap
      name: "RSA-PSS",
      hash: "SHA-256"
    },             
    true,                  // extractability of key to unwrap
    ["sign", "verify"]               // key usages for key to unwrap
  );

}

function generateKeys() {
        window.crypto.subtle.generateKey(
          {
            name: "RSA-PSS",
            // Consider using a 4096-bit key for systems that require long-term security
            modulusLength: 2048,
            publicExponent: new Uint8Array([1, 0, 1]),
            hash: "SHA-256",
          },
          true,
          ["sign", "verify"]
        )
        .then((keyPair) => {
            console.log('Key pair has been generated!');
            //wrap key
            return wrapCryptoKey(keyPair.privateKey);
        })
        .then((wrappedKey) => {
            console.log('Trying to log wrapped key');
            console.log(wrappedKey);
            let nArr = new Uint8Array(wrappedKey);
            console.log('UInt8 '+nArr);
            console.log('In base64');
            console.log('Trying to unwrap key');
            unwrapPrivateKey(nArr);
        });
}

标签: javascriptencryption

解决方案


unwrapKey返回在当前情况下Promise提供CryptoKey封装私钥的 a。由于私钥只能用于签名而不用于验证,verify因此必须从keyUsages传递给的 -parameter中删除unwrapKey,即keyUsages-parameter 必须仅包含sign

...
const unwrappedPKey = await window.crypto.subtle.unwrapKey(
    "pkcs8",               // import format
    wrappedKeyBuffer,      // ArrayBuffer representing key to unwrap
    unwrappingKey,         // CryptoKey representing key encryption key
    {                      // algorithm params for key encryption key
        name: "AES-GCM",
        iv: iv
    },
    {                      // algorithm params for key to unwrap
        name: "RSA-PSS",
        hash: "SHA-256"
    },             
    true,                  // extractability of key to unwrap
    ["sign"]               // key usages for key to unwrap          <-- verify removed!
);
...

另请参阅此处,展开 pkcs8 密钥部分以获取适当的示例。


推荐阅读