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

  • 生成RSA-PSS密钥对;
  • 使用AES-GCM包装私钥;
  • 有机会解开步骤2的结果。

  • 我知道我必须存储AES-GCM的盐和InitialVector。但是,当我尝试解开包装结果(ArrayBuffer)时,它失败了:
    DataError - Data provided to an operation does not meet requirements.
    

    我试图将一个空的ArrayBuffer传递给unwrapKey,但它失败并显示OperationError。我还尝试将结果从wrapKey直接传递给unwrapKey(不转换为UInt8Array),但是结果是相同的。

    我使用相同的盐,IV和密钥 Material 来导出加密/解密密钥。

    我不知道出什么问题了。

    我的代码:
    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);
            });
    }
    
    

    最佳答案

    unwrapKey 返回一个Promise,它提供了在当前情况下封装了私钥的CryptoKey。由于私钥只能用于签名而不能用于验证,因此必须从传递给verifykeyUsages -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!
    );
    ...
    

    有关适当的示例,另请参见here, section Unwrapping a pkcs8 key

    09-13 00:16