4
I’m using PBKDF2 in Webcryptoapi to generate a "derivable" key based on a user input (a password) and derive a key from it AES-GCM.
I’m doing a round of tests where:
- in the first round Gero the keys (PBKDF2 and AES-GCM), except this object
CryptoKey
(AES-GCM) in a variable in the upper scope, Gero a "iv", encrypt string "Hello World!" export key AES-GCM in an objectjwk
- in the second round decrypt using the iv and the key AES-GCM
- in the third I import the key AES-GCM of format
jwk
use the iv and description - in the fourth round a new key (PBKDF2 and AES-GCM), use this new key AES-GCM with the iv to decrypt
Nothing unusual between the first ha third bad round (the devil resides in the "bad"), the fourth round where a new key is generated, effectively decrypting the string...
In my test I am using a string with 4 zeros ("0000") as a password for these keys (for PBKDF2 and AES-GCM) but I wonder if:
- even if, a key is generated using the same password used in another key should not be two separate keys?
The snippet below expresses this question:
let cry = document.getElementById('cry')
let dec1 = document.getElementById('dec-1')
let dec2 = document.getElementById('dec-2')
let dec3 = document.getElementById('dec-3')
let logger = document.getElementById('logger')
const UTILS = {
convertStringToArrayBuffer(str) {
let encoder = new TextEncoder('utf-8')
return encoder.encode(str)
},
convertArrayBuffertoString(buffer) {
let decoder = new TextDecoder('utf-8')
return decoder.decode(buffer)
},
bufferToHex(arr) {
let i,
len,
hex = '',
c
for (i = 0, len = arr.length; i < len; i += 1) {
c = arr[i].toString(16)
if ( c.length < 2 ) {
c = '0' + c
}
hex += c
}
return hex
},
hexToBuffer(hex) {
let i,
byteLen = hex.length / 2,
arr,
j = 0
if ( byteLen !== parseInt(byteLen, 10) ) {
throw new Error("Invalid hex length '" + hex.length + "'")
}
arr = new Uint8Array(byteLen)
for (i = 0; i < byteLen; i += 1) {
arr[i] = parseInt(hex[j] + hex[j + 1], 16)
j += 2
}
return arr
}
}
const WebCryptoGenerateKey = (password) => {
return crypto.subtle.importKey(
'raw',
UTILS.convertStringToArrayBuffer(password),
{
name: 'PBKDF2'
},
false, // PBKDF2 don't exportable
[ 'deriveKey', 'deriveBits' ]
)
}
const AES_GCM = (CryptoKey, opts) => {
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: new Uint8Array(UTILS.convertStringToArrayBuffer(opts.password)),
iterations: 100000, // mobile = 100 000, desktop.32bit = 1 000 000, desktop.64bit = 10 000 000
hash: 'SHA-256'
},
CryptoKey,
{
name: 'AES-GCM',
length: 256
},
opts.export, // Extractable is set to false so that underlying key details cannot be accessed.
[ 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey' ]
)
}
const AES_GCM_ENCRYPT = (data, key, iv) => {
return crypto.subtle.encrypt(
{
name: "AES-GCM",
// Don't re-use initialization vectors!
// Always generate a new iv every time your encrypt!
// Recommended to use 12 bytes length
iv: iv,
// Additional authentication data (optional)
//additionalData: ArrayBuffer,
// Tag length (optional)
length: 256, //can be 32, 64, 96, 104, 112, 120 or 128 (default)
},
key, // from generateKey or importKey above
data // ArrayBuffer of data you want to encrypt
)
}
const AES_GCM_DECRYPT = (enc, key, iv) => {
return crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv, // The initialization vector you used to encrypt
//additionalData: ArrayBuffer, //The addtionalData you used to encrypt (if any)
length: 256, //The tagLength you used to encrypt (if any)
},
key, //from generateKey or importKey above
enc //ArrayBuffer of the data
)
}
const AES_GCM_IMPORT = (jwk) => {
return crypto.subtle.importKey(
"jwk", //can be "jwk" or "raw"
jwk,
{ //this is the algorithm options
name: "AES-GCM",
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can "encrypt", "decrypt", "wrapKey", or "unwrapKey"
)
}
const AES_GCM_EXPORT = (key) => {
return crypto.subtle.exportKey(
"jwk", //can be "jwk" or "raw"
key //extractable must be true
)
}
let AES_GCM_KEY,
EXPORTED_AES_GCM_JWK,
IMPORTED_AES_GCM_JWK,
ENCRYPTED
let vector = crypto.getRandomValues(new Uint8Array(16))
cry.addEventListener('click', function() {
WebCryptoGenerateKey('0000').then(CryptoKey => {
AES_GCM(CryptoKey, {
password: '0000',
export: true
}).then(aes_gcm => {
AES_GCM_KEY = aes_gcm
AES_GCM_ENCRYPT(UTILS.convertStringToArrayBuffer('Hello World!'), aes_gcm, vector).then(encData => {
ENCRYPTED = UTILS.bufferToHex(new Uint8Array(encData))
logger.innerHTML += '<br>' + ENCRYPTED
})
AES_GCM_EXPORT(aes_gcm).then(jwk => {
EXPORTED_AES_GCM_JWK = jwk
})
})
})
}, false)
dec1.addEventListener('click', function() {
AES_GCM_DECRYPT(UTILS.hexToBuffer(ENCRYPTED), AES_GCM_KEY, vector).then(result => {
logger.innerHTML += '<br>' + UTILS.convertArrayBuffertoString(result)
})
}, false)
dec2.addEventListener('click', function() {
AES_GCM_IMPORT(EXPORTED_AES_GCM_JWK).then(aes_gcm => {
AES_GCM_DECRYPT(UTILS.hexToBuffer(ENCRYPTED), aes_gcm, vector).then(result => {
logger.innerHTML += '<br>' + UTILS.convertArrayBuffertoString(result)
})
})
}, false)
dec3.addEventListener('click', function() {
WebCryptoGenerateKey('0000').then(CryptoKey => {
AES_GCM(CryptoKey, {
password: '0000',
export: true
}).then(aes_gcm2 => {
AES_GCM_DECRYPT(UTILS.hexToBuffer(ENCRYPTED), aes_gcm2, vector).then(result => {
logger.innerHTML += '<br>' + UTILS.convertArrayBuffertoString(result)
})
})
})
}, false)
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<button id="cry" type="button" class="btn btn-secondary">crypt</button>
<button id="dec-1" type="button" class="btn btn-secondary">decrypt 1</button>
<button id="dec-2" type="button" class="btn btn-secondary">decrypt 2</button>
<button id="dec-3" type="button" class="btn btn-secondary">decrypt 3</button>
<div id="logger" class="col-12 mx-auto mt-3"></div>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"></script>
Thanks for your reply, I got an idea of how it works better than the Mozilla documentation explains. Could you tell me what shape this is WKS? I saw in the documentation only: raw, jwk, spki and pkcs8. And without wanting to seem abused, what length would be suitable for salt ... vi in several places sizes with 8, 12 or 16 more, found no reference on this length in relation to the algorithm and its "length" (there is this relation)?
– Lauro Moraes
The document RFC 8018 recommends 64bits, but the document NIST SP 800-132 recommends 128bits. In this case, I get the largest, a 128bit salt. For AES-GCM IV the indicated is 96bits.
– Marcos Zolnowski
WKS was a typo, the correct is JWK.
– Marcos Zolnowski
To iv I had already seen the NIST 800-38d in section 5.2.1.1 already recommended 96bits for iv ... I thank you once again for the reply and the guidelines.
– Lauro Moraes
That’s the document. My first guess would be to say that the ideal size of IV for a block Cipher is always the size of the block. But how is the mode of operation that decides what to do with the IV, he’s the one who makes the rules.
– Marcos Zolnowski