WebCryptoApi: Cannot wrap&unwrap aes-gcm key into&from "jwk" format with "encrypt" and "decrypt" active - webcrypto-api

I'm generating a key for encrypting data, then wrap it using a master key and store it alongside the encrypted data. All is well when wrapping into raw format, but when wrapping as jwk I get the error DOMException: Data provided to an operation does not meet requirements.
It works when specifying they key for being used either for encryption or for decryption, but not when both are specified as key usages.
let wrapAlgo = {
name: "AES-KW",
length: 256
};
let encAlgo = {
name:"AES-GCM",
length:256
}
let format = "jwk";
let extractable=true;
let keyUsages = ["encrypt", "decrypt"];
let kek = await crypto.subtle.generateKey(
wrapAlgo,
false,
["wrapKey", "unwrapKey"]
);
let key = await window.crypto.subtle.generateKey(
encAlgo,
extractable, // the key is extractable (i.e. can be used in exportKey)
keyUsages
);
console.log("key", key);
let wrappedKey = await crypto.subtle.wrapKey(
format,
key,
kek,
wrapAlgo
);
console.log("wrappedKey", wrappedKey);
let unwrappedKey = await crypto.subtle.unwrapKey(
format,
wrappedKey,
kek,
wrapAlgo,
encAlgo,
extractable,
keyUsages
);
console.log("key", await crypto.subtle.exportKey("jwk", unwrappedKey));

AES-KW is a key wrap algorithm described in RFC3394. The algorithm is used to wrap i. e. encrypt a key. The input, i.e. the key to be encrypted, must be an integer multiple of 8 bytes, s. also here.
The key to be encrypted is passed in SubtleCrypto.wrapKey() in the 2nd parameter key as CryptoKey and must therefore be exported before the actual encryption. For this purpose the format in which the key is exported is specified in the 1st parameter format:
const result = crypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgo);
In the posted example, the key to be wrapped is a 32 bytes key for AES-256. In raw format, the key thus satisfies the AES-KW length criterion. In jwk format, however, the length criterion is generally not met:
If the key exported in jwk format is serialized, it has a length for the key usage ["encrypt"] or ["decrypt"] that happens to be an integer multiple of 8 bytes (112 bytes), while this is not the case for the key usages ["encrypt", "decrypt"] (122 bytes):
(async () => {
async function getLength(keyUsages) {
var key = await window.crypto.subtle.generateKey(
{name:"AES-GCM", length: 256},
true,
keyUsages
);
var expkey = await crypto.subtle.exportKey("jwk", key)
var expkeySerLen = JSON.stringify(expkey).length;
return {KeyUsages: keyUsages, length: expkeySerLen, lenMod8: expkeySerLen % 8};
}
console.log(await getLength(["encrypt"])); // works
console.log(await getLength(["decrypt"])); // works
console.log(await getLength(["encrypt", "decrypt"])); // doesn't work
})();
This is most likely the reason why the code with the key usage ["encrypt"] or ["decrypt"] is executed, but not the code for the key usages ["encrypt", "decrypt"].
The bottom line is that AES-KW works reliably for the raw format, but not for the jwk format.
However, the jwk format can be used in SubtleCrypto.wrapKey() for other wrapping algorithms, such as AES-GCM:
(async () => {
let encAlgo = {
name:"AES-GCM",
length:256
};
let wrapAlgo = {
name:"AES-GCM",
length:256
};
let aesGcmParams = {
name:"AES-GCM",
iv: window.crypto.getRandomValues(new Uint8Array(12))
};
let format = "jwk";
let extractable=true;
let keyUsages = ["encrypt", "decrypt"];
let kek = await crypto.subtle.generateKey(
wrapAlgo,
false,
["wrapKey", "unwrapKey"]
);
let key = await window.crypto.subtle.generateKey(
encAlgo,
extractable, // the key is extractable (i.e. can be used in exportKey)
keyUsages
);
console.log("key (CryptoKey)", key);
console.log("key (jwk)", await crypto.subtle.exportKey("jwk", key));
let wrappedKey = await crypto.subtle.wrapKey(
format,
key,
kek,
aesGcmParams
);
console.log("wrappedKey (ArrayBuffer)", wrappedKey);
let unwrappedKey = await crypto.subtle.unwrapKey(
format,
wrappedKey,
kek,
aesGcmParams,
encAlgo,
extractable,
keyUsages
);
console.log("unwrappedKey (jwk) ", await crypto.subtle.exportKey("jwk", unwrappedKey));
})();

Related

How to decrypt NodeJS crypto on the client side with a known encryption key?

I am trying to have AES encryption on the server side, and decryption on the client side. I have followed an example where CryptoJS is used on the client side for encryption and SubtleCrypto on the client side as well for decryption, but in my case I have the encryption and decryption separated.
Suppose I have the following encryption function within React Native:
const encrypt = (str: string) => {
const iv = crypto.randomBytes(12);
const myHexToken = "0x...."
const cipher = crypto.createCipheriv('aes-256-gcm', myHexToken.slice(0,32), iv)
let encrypted = cipher.update(str, 'utf8', 'hex')
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
message: encrypted,
tag: tag.toString('hex'),
iv: iv.toString('hex'),
};
};
This json is then posted to the client through a webview postMessage.
The client side has the following javascript injected:
var myHexToken = "0x....";
window.addEventListener("message", async function (event) {
var responseData = JSON.parse(event.data);
try {
var decryptedData = await decrypt(responseData.iv, responseData.message, responseData.tag);
} catch (e) {
alert(e);
}
// ...
How can I decrypt responseData.message within the WebView through SubtleCrypto of the Web Crypto API?
I have tried various things with the following methods, but I keep getting "OperationalError":
function fromHex(hexString) {
return new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function fromBase64(base64String) {
return Uint8Array.from(window.atob(base64String), c => c.charCodeAt(0));
}
async function importKey(rawKey) {
var key = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-GCM",
true,
["encrypt", "decrypt"]
);
return key;
}
async function decrypt(iv, data, tag) {
var rawKey = fromHex(myHexToken.slice(0,32));
var iv = fromHex(iv);
var ciphertext = str2ab(data + tag);
var cryptoKey = await importKey(rawKey)
var decryptedData = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
cryptoKey,
ciphertext
)
var decoder = new TextDecoder();
var plaintext = decoder.decode(decryptedData);
return plaintext;
}
UPDATE 1: Added the getAuthTag implementation server side. Changed IV to have length of 12 bytes. Attempt to concatenate ciphertext and tag client side.
I have verified that "myHexToken" is the same both client and server side. Also, the return values of the server side "encrypt()" method are correctly sent to the client.
In the WebCrypto code the key must not be hex decoded with fromHex(), but must be converted to an ArrayBuffer with str2ab().
Also, the concatenation of ciphertext and tag must not be converted to an ArrayBuffer with str2ab(), but must be hex decoded with fromHex().
With these fixes decryption works:
Test:
For the test, the following hex encoded key and plaintext are used on the NodeJS side:
const myHexToken = '000102030405060708090a0b0c0d0e0ff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff';
const plaintext = "The quick brown fox jumps over the lazy dog";
const encryptedData = encrypt(plaintext);
console.log(encryptedData);
This results e.g. in the following output:
{
message: 'cc4beae785cda5c9413f49cf9449a6ae17fdc0f7435b9a8fd954602bdb4f4b825793f6b561c0d9a709007c',
tag: '046c8e56bbd13db2faed82d1b19c665e',
iv: '11f87b0eaf006373ae8bc94d'
}
The ciphertext created this way can be successfully decrypted with the fixed JavaScript code:
(async () => {
function fromHex(hexString) {
return new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
async function importKey(rawKey) {
var key = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-GCM",
true,
["encrypt", "decrypt"]
);
return key;
}
async function decrypt(iv, data, tag) {
//var rawKey = fromHex(myHexToken.slice(0,32)); // Fix 1
var rawKey = str2ab(myHexToken.slice(0,32));
var iv = fromHex(iv);
//var ciphertext = str2ab(data + tag); // Fix 2
var ciphertext = fromHex(data + tag);
var cryptoKey = await importKey(rawKey)
var decryptedData = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
cryptoKey,
ciphertext
);
var decoder = new TextDecoder();
var plaintext = decoder.decode(decryptedData);
return plaintext;
}
var myHexToken = '000102030405060708090a0b0c0d0e0ff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
var data = {
message: 'cc4beae785cda5c9413f49cf9449a6ae17fdc0f7435b9a8fd954602bdb4f4b825793f6b561c0d9a709007c',
tag: '046c8e56bbd13db2faed82d1b19c665e',
iv: '11f87b0eaf006373ae8bc94d'
}
var plaintext = await decrypt(data.iv, data.message, data.tag);
console.log(plaintext);
})();
A remark about the key: In the posted NodeJS code, const myHexToken = "0x...." is set. It's not clear to me if the 0x prefix is just supposed to symbolize a hex encoded string, or is really contained in the string. If the latter, it should actually be removed before the implicit UTF-8 encoding (by createCiperiv()). In case of a hex decoding it must be removed anyway.
In the posted example a valid hex encoded 32 bytes key is used (i.e. without 0x prefix).
With regard to the key encoding, also note the following:
The conversion of the key from a hex encoded string by a UTF-8 (or ASCII) encoding results in only half of the key being considered, in the example: 000102030405060708090a0b0c0d0e0f. This reduces security, because the value range per byte is reduced from 256 to 16 values.
In order for the entire key to be considered, the correct conversion on the NodeJS side would be: Buffer.from(myHexToken, 'hex') and on the WebCrypto side: var rawKey = fromHex(myHexToken).
Because of its implicit UTF8 encoding crypto.createCipheriv(..., myHexToken.slice(0,32), ...) creates a 32 bytes key and is functionally identical to str2ab(myHexToken.slice(0,32)) only as long as the characters in the substring myHexToken.slice(0,32) correspond to ASCII characters (which is true for a hex encoded string).

How can I choose my own smart contract addresses on RSK before deploying it?

I have noticed that some smart contracts
have addresses that start with 0x0000, 0x1234, or0x1337 - and I'm not sure how the deployers managed to do this.
​
Is it possible to do this on RSK?
Building upon the previous answer,
using the CREATE2 opcode -
it is not only possible to predict the address
that a smart contract will be deployed at,
but also (partially) choose the address
that you would like it to be deployed at.
For example, if you want a vanity address that starts
with 4 leading zeroes, you use the following helper function,
which checks for a match of the required vanity address:
function hasLeadingZeros(address = '0x0000', zeros = 4) {
for (let i = 2; i <= zeros + 1; i += 1) {
if (address.charAt(i) !== '0') return false;
}
return true;
}
The next helper function uses trial and error,
since keccak256 as with other hashes are 1-way functions,
to determine which salt parameter will result
in obtaining a required vanity address.
function getSaltForVanityAddress(deployerAddress, initCodeHash) {
let salt = 0;
let vanityAddress = '';
let saltHash;
while (!hasLeadingZeros(vanityAddress)) {
salt += 1;
saltHash = ethers.utils.keccak256(salt);
vanityAddress = ethers.utils.getCreate2Address(
deployerAddress,
saltHash,
initCodeHash,
);
}
return saltHash;
}
By combine the above, you can use
factory.deployCreate2(salt);
to deploy a smart contract with the address needed.
Here is a test case to illustrate:
it('Should deploy Child to the address starting with four zeros', async () => {
const Child = await ethers.getContractFactory('Child');
const { data: initCode } =
Child.getDeployTransaction(deployer.address);
const initCodeHash = ethers.utils.keccak256(initCode);
const salt = getSaltForVanityAddress(factory.address, initCodeHash);
const deployTx = await factory.deployCreate2(salt);
const txReceipt = await deployTx.wait();
const deployEvent = txReceipt.events.find(
(event) => event.event === 'ContractDeployed',
);
expect(hasLeadingZeros(
deployEvent.args.childContract)
).to.be.true;
console.log(
`Found salt '${salt}' such that the\ndeployed s/c address is ${deployEvent.args.childContract}`,
);
});
Found salt '0xb20ba450430132a5f0191bd27fc7e8018b39435484af81bbdf289296ae61ea75' such that the
deployed s/c address is 0x0000b36619Df7e2816577549b78CBB8b189e8d6B
✔ Should deploy Child to the address starting with four zeros (4973ms)

Is it possible to encrypt / decrypt a Sequelize model's field with a unique validation?

for the GDPR I'm trying to encrypt and decrypt my user's personal data from my Sequelize Users model. My username and email fields must be unique.
I tried to use the afterValidate and the beforeValidate hooks, but on validation, Sequelize can't recognize if the fields are unique or not because they already are encrypted, and a crypt is always different.
How do you handle this?
I think you are using random initVector and Securitykey,which will be different in different times. So you have to use a const value which can be saved in a config file.
Ex :
initVectorString: 'xx567876567898765',
securitykeyString:
'Zzzz345678909876545678984567890',
After saving in the config file, you can have a encrypt and decrypt function as follows
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const initVector = Buffer.from(initVectorString, 'hex');
const Securitykey = Buffer.from(securitykeyString, 'hex');
function encryptData(data) {
try {
const cipher = crypto.createCipheriv(algorithm, Securitykey, initVector);
let encryptedData = cipher.update(data, 'utf-8', 'hex');
encryptedData += cipher.final('hex');
return encryptedData;
} catch (error) {}
}
function decryptData(data) {
try {
const decipher = crypto.createDecipheriv(
algorithm,
Securitykey,
initVector
);
let decryptedData = decipher.update(data, 'hex', 'utf-8');
decryptedData += decipher.final('utf8');
return decryptedData;
} catch (error) {}
}

Keep getting Timestamp error on Coinbase API Call

I'm trying to connect my Google Sheet to Coinbase API using apps script. When I try to use the authentication with my keys, I keep getting the same error:
{"errors":[{"id":"authentication_error","message":"invalid timestamp"}]}
(Code 401).
I try to check the time difference between my request and Coinbase server (to see if it is more than 30 seconds) and it doesn't. (1589465439 (mine) / 1589465464 (server)).
My code:
var timestamp = Math.floor(Date.now() / 1000) + 15;
Logger.log(timestamp);
var req = {
method: 'GET',
path: '/v2/accounts',
body: ''
};
var message = timestamp + req.method + req.path + req.body;
var secret = Utilities.base64Decode(apiKey);
secret = Utilities.newBlob(secret).getDataAsString();
//create a hexedecimal encoded SHA256 signature of the message
var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, message, secret);
var signature = Utilities.base64Encode(hmac);
Logger.log(signature);
var signatureStr = '';
for (i = 0; i < signature.length; i++) {
var byte = signature[i];
if (byte < 0)
byte += 256;
var byteStr = byte.toString(16);
// Ensure we have 2 chars in our byte, pad with 0
if (byteStr.length == 1) byteStr = '0' + byteStr;
signatureStr += byteStr;
}
Logger.log(signatureStr);
var options = {
baseUrl: 'https://api.coinbase.com/',
url: req.path,
method: req.method,
headers: {
'CB-ACCESS-SIGN': signatureStr,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': apiKey
}
};
var response = UrlFetchApp.fetch("https://api.coinbase.com/v2/accounts", options);
This is an old question, but in case it's still unsolved, I see a few changes that will fix this. I ran into a similar issue and had to solve it.
You should have 2 keys total (1 API key, 1 secret Key). Secret key is a separate key that comes from Coinbase, and is not a decoded variant of the API access key. It does not need explicit decoding.
Where you're passing timestamp as a header -> convert that value to a string to fix the timestamp error.
headers: {
'CB-ACCESS-SIGN': signatureStr,
'CB-ACCESS-TIMESTAMP': timestamp.toString(),
'CB-ACCESS-KEY': apiKey
}
You can collapse the hmac and signature variables into this one liner before converting to hex.
let signature = Utilities.computeHmacSha256Signature(message, secret);
These 3 changes make your code start to work with my keys.

HCL Domino AppDevPack - Problem with writing Rich Text

I use the code proposed as an example in the documentation for Domino AppDev Pack 1.0.4 , the only difference is the reading of a text file (body.txt) as a buffer, this file containing only simple long text (40Ko).
When it is executed, the document is created in the database and the rest of the code does not return an error.
But finally, the rich text field was not added to the document.
Here the response returned:
response: {"fields":[{"fieldName":"Body","unid":"8EA69129BEECA6DEC1258554002F5DCD","error":{"name":"ProtonError","code":65577,"id":"RICH_TEXT_STREAM_CORRUPT"}}]}
My goal is to write very long text (more than 64 Ko) in a rich text field. I use in the example a text file for the buffer but it could be later something like const buffer = Buffer.from ('very long text ...')
Is this the right way or does it have to be done differently ?
I'm using a Windows system with IBM Domino (r) Server (64 Bit), Release 10.0.1FP4 and AppDevPack 1.0.4.
Thank you in advance for your help
Here's code :
const write = async (database) => {
let writable;
let result;
try {
// Create a document with subject write-example-1 to hold rich text
const unid = await database.createDocument({
document: {
Form: 'RichDiscussion',
Title: 'write-example-1',
},
});
writable = await database.bulkCreateRichTextStream({});
result = await new Promise((resolve, reject) => {
// Set up event handlers.
// Reject the Promise if there is a connection-level error.
writable.on('error', (e) => {
reject(e);
});
// Return the response from writing when resolving the Promise.
writable.on('response', (response) => {
console.log("response: " + JSON.stringify(response));
resolve(response);
});
// Indicates which document and item name to use.
writable.field({ unid, fieldName: 'Body' });
let offset = 0;
// Assume for purposes of this example that we buffer the entire file.
const buffer = fs.readFileSync('/driver/body.txt');
// When writing large amounts of data, it is necessary to
// wait for the client-side to complete the previous write
// before writing more data.
const writeData = () => {
let draining = true;
while (offset < buffer.length && draining) {
const remainingBytes = buffer.length - offset;
let chunkSize = 16 * 1024;
if (remainingBytes < chunkSize) {
chunkSize = remainingBytes;
}
draining = writable.write(buffer.slice(offset, offset + chunkSize));
offset += chunkSize;
}
if (offset < buffer.length) {
// Buffer is not draining. Whenever the drain event is emitted
// call this function again to write more data.
writable.once('drain', writeData);
}
};
writeData();
writable = undefined;
});
} catch (e) {
console.log(`Unexpected exception ${e.message}`);
} finally {
if (writable) {
writable.end();
}
}
return result;
};
As of appdev pack 1.0.4, the rich text stream accepts writing data of valid rich text cd format, in the LMBCS character set. We are currently working on a library to help you write valid rich text data to the stream.
I'd love to hear more about your use cases, and we're excited you're already poking around the feature! If you can join the openntf slack channel, I usually hang out there.