Support AES-GCM with AEAD Protected Data Packets
Closes openpgpjs/openpgpjs#421
This commit is contained in:
parent
c9b20c96e0
commit
ded8926b27
|
@ -37,6 +37,7 @@ export default {
|
||||||
prefer_hash_algorithm: enums.hash.sha256,
|
prefer_hash_algorithm: enums.hash.sha256,
|
||||||
encryption_cipher: enums.symmetric.aes256,
|
encryption_cipher: enums.symmetric.aes256,
|
||||||
compression: enums.compression.zip,
|
compression: enums.compression.zip,
|
||||||
|
aead_protect: true, // use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption
|
||||||
integrity_protect: true, // use integrity protection for symmetric encryption
|
integrity_protect: true, // use integrity protection for symmetric encryption
|
||||||
ignore_mdc_error: false, // fail on decrypt if message is not integrity protected
|
ignore_mdc_error: false, // fail on decrypt if message is not integrity protected
|
||||||
rsa_blinding: true,
|
rsa_blinding: true,
|
||||||
|
|
105
src/crypto/gcm.js
Normal file
105
src/crypto/gcm.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// OpenPGP.js - An OpenPGP implementation in javascript
|
||||||
|
// Copyright (C) 2016 Tankred Hase
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
|
// License as published by the Free Software Foundation; either
|
||||||
|
// version 3.0 of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview This module wraps native AES-GCM en/decryption for both
|
||||||
|
* the WebCrypto api as well as node.js' crypto api.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import util from '../util.js';
|
||||||
|
import asmCrypto from 'asmcrypto-lite';
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
const Buffer = util.getNodeBuffer();
|
||||||
|
|
||||||
|
export const ivLength = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt plaintext input.
|
||||||
|
* @param {String} cipher The symmetric cipher algorithm to use
|
||||||
|
* @param {Uint8Array} plaintext The cleartext input to be encrypted
|
||||||
|
* @param {Uint8Array} key The encryption key
|
||||||
|
* @param {Uint8Array} iv The initialization vector (12 bytes)
|
||||||
|
* @return {Promise<Uint8Array>} The ciphertext output
|
||||||
|
*/
|
||||||
|
export function encrypt(cipher, plaintext, key, iv) {
|
||||||
|
if (cipher.substr(0,3) !== 'aes') {
|
||||||
|
return Promise.reject(new Error('Invalid cipher for GCM mode'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webCrypto) { // native WebCrypto api
|
||||||
|
const keyOptions = {
|
||||||
|
name: 'AES-GCM'
|
||||||
|
},
|
||||||
|
encryptOptions = {
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv: iv
|
||||||
|
};
|
||||||
|
return webCrypto.importKey('raw', key, keyOptions, false, ['encrypt']).then(keyObj => {
|
||||||
|
return webCrypto.encrypt(encryptOptions, keyObj, plaintext);
|
||||||
|
}).then(ciphertext => {
|
||||||
|
return new Uint8Array(ciphertext);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if(nodeCrypto) { // native node crypto library
|
||||||
|
let cipherObj = new nodeCrypto.createCipheriv('aes-' + cipher.substr(3,3) + '-gcm', new Buffer(key), new Buffer(iv));
|
||||||
|
let encrypted = Buffer.concat([cipherObj.update(new Buffer(plaintext)), cipherObj.final()]);
|
||||||
|
return Promise.resolve(new Uint8Array(encrypted));
|
||||||
|
|
||||||
|
} else { // asm.js fallback
|
||||||
|
return Promise.resolve(asmCrypto.AES_GCM.encrypt(plaintext, key, iv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt ciphertext input
|
||||||
|
* @param {String} cipher The symmetric cipher algorithm to use
|
||||||
|
* @param {Uint8Array} ciphertext The ciphertext input to be decrypted
|
||||||
|
* @param {Uint8Array} key The encryption key
|
||||||
|
* @param {Uint8Array} iv The initialization vector (12 bytes)
|
||||||
|
* @return {Promise<Uint8Array>} The plaintext output
|
||||||
|
*/
|
||||||
|
export function decrypt(cipher, ciphertext, key, iv) {
|
||||||
|
if (cipher.substr(0,3) !== 'aes') {
|
||||||
|
return Promise.reject(new Error('Invalid cipher for GCM mode'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webCrypto) { // native WebCrypto api
|
||||||
|
const keyOptions = {
|
||||||
|
name: 'AES-GCM'
|
||||||
|
},
|
||||||
|
decryptOptions = {
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv: iv
|
||||||
|
};
|
||||||
|
return webCrypto.importKey('raw', key, keyOptions, false, ['decrypt']).then(keyObj => {
|
||||||
|
return webCrypto.decrypt(decryptOptions, keyObj, ciphertext);
|
||||||
|
}).then(plaintext => {
|
||||||
|
return new Uint8Array(plaintext);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if(nodeCrypto) { // native node crypto library
|
||||||
|
let decipherObj = new nodeCrypto.createDecipheriv('aes-' + cipher.substr(3,3) + '-gcm', new Buffer(key), new Buffer(iv));
|
||||||
|
let decrypted = Buffer.concat([decipherObj.update(new Buffer(ciphertext)), decipherObj.final()]);
|
||||||
|
return Promise.resolve(new Uint8Array(decrypted));
|
||||||
|
|
||||||
|
} else { // asm.js fallback
|
||||||
|
return Promise.resolve(asmCrypto.AES_GCM.decrypt(ciphertext, key, iv));
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
import cipher from './cipher';
|
import cipher from './cipher';
|
||||||
import hash from './hash';
|
import hash from './hash';
|
||||||
import cfb from './cfb';
|
import cfb from './cfb';
|
||||||
|
import * as gcm from './gcm';
|
||||||
import publicKey from './public_key';
|
import publicKey from './public_key';
|
||||||
import signature from './signature';
|
import signature from './signature';
|
||||||
import random from './random';
|
import random from './random';
|
||||||
|
@ -21,6 +22,8 @@ const mod = {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
/** @see module:crypto/cfb */
|
/** @see module:crypto/cfb */
|
||||||
cfb: cfb,
|
cfb: cfb,
|
||||||
|
/** @see module:crypto/aes-gcm */
|
||||||
|
gcm: gcm,
|
||||||
/** @see module:crypto/public_key */
|
/** @see module:crypto/public_key */
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
/** @see module:crypto/signature */
|
/** @see module:crypto/signature */
|
||||||
|
|
|
@ -91,6 +91,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No secure random number generator available.');
|
throw new Error('No secure random number generator available.');
|
||||||
}
|
}
|
||||||
|
return buf;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -94,7 +94,8 @@ export default {
|
||||||
publicSubkey: 14,
|
publicSubkey: 14,
|
||||||
userAttribute: 17,
|
userAttribute: 17,
|
||||||
symEncryptedIntegrityProtected: 18,
|
symEncryptedIntegrityProtected: 18,
|
||||||
modificationDetectionCode: 19
|
modificationDetectionCode: 19,
|
||||||
|
symEncryptedAEADProtected: 20 // see IETF draft: https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.2.1
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Data types in the literal packet
|
/** Data types in the literal packet
|
||||||
|
|
|
@ -96,15 +96,23 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) {
|
||||||
if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
|
if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
|
||||||
throw new Error('Invalid session key for decryption.');
|
throw new Error('Invalid session key for decryption.');
|
||||||
}
|
}
|
||||||
var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected);
|
|
||||||
|
var symEncryptedPacketlist = this.packets.filterByTag(
|
||||||
|
enums.packet.symmetricallyEncrypted,
|
||||||
|
enums.packet.symEncryptedIntegrityProtected,
|
||||||
|
enums.packet.symEncryptedAEADProtected
|
||||||
|
);
|
||||||
|
|
||||||
if (symEncryptedPacketlist.length !== 0) {
|
if (symEncryptedPacketlist.length !== 0) {
|
||||||
var symEncryptedPacket = symEncryptedPacketlist[0];
|
var symEncryptedPacket = symEncryptedPacketlist[0];
|
||||||
symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data);
|
return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => {
|
||||||
var resultMsg = new Message(symEncryptedPacket.packets);
|
var resultMsg = new Message(symEncryptedPacket.packets);
|
||||||
// remove packets after decryption
|
// remove packets after decryption
|
||||||
symEncryptedPacket.packets = new packet.List();
|
symEncryptedPacket.packets = new packet.List();
|
||||||
return resultMsg;
|
return resultMsg;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,18 +227,21 @@ Message.prototype.encrypt = function(keys, passwords) {
|
||||||
var packetlist = msg.packets;
|
var packetlist = msg.packets;
|
||||||
|
|
||||||
var symEncryptedPacket;
|
var symEncryptedPacket;
|
||||||
if (config.integrity_protect) {
|
if (config.aead_protect) {
|
||||||
|
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
|
||||||
|
} else if (config.integrity_protect) {
|
||||||
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
|
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
|
||||||
} else {
|
} else {
|
||||||
symEncryptedPacket = new packet.SymmetricallyEncrypted();
|
symEncryptedPacket = new packet.SymmetricallyEncrypted();
|
||||||
}
|
}
|
||||||
symEncryptedPacket.packets = this.packets;
|
symEncryptedPacket.packets = this.packets;
|
||||||
symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey);
|
|
||||||
packetlist.push(symEncryptedPacket);
|
|
||||||
// remove packets after encryption
|
|
||||||
symEncryptedPacket.packets = new packet.List();
|
|
||||||
|
|
||||||
return msg;
|
return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey).then(() => {
|
||||||
|
packetlist.push(symEncryptedPacket);
|
||||||
|
// remove packets after encryption
|
||||||
|
symEncryptedPacket.packets = new packet.List();
|
||||||
|
return msg;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -169,17 +169,16 @@ export function decryptKey({ privateKey, passphrase }) {
|
||||||
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) {
|
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) {
|
||||||
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
||||||
|
|
||||||
if (asyncProxy) { // use web worker if available
|
if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||||
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor });
|
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor });
|
||||||
}
|
}
|
||||||
|
|
||||||
return execute(() => {
|
let message = createMessage(data, filename);
|
||||||
|
if (privateKeys) { // sign the message only if private keys are specified
|
||||||
|
message = message.sign(privateKeys);
|
||||||
|
}
|
||||||
|
|
||||||
let message = createMessage(data, filename);
|
return message.encrypt(publicKeys, passwords).then(message => {
|
||||||
if (privateKeys) { // sign the message only if private keys are specified
|
|
||||||
message = message.sign(privateKeys);
|
|
||||||
}
|
|
||||||
message = message.encrypt(publicKeys, passwords);
|
|
||||||
|
|
||||||
if(armor) {
|
if(armor) {
|
||||||
return {
|
return {
|
||||||
|
@ -190,7 +189,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
}, 'Error encrypting message');
|
}).catch(onError.bind(null, 'Error encrypting message'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,20 +208,19 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
|
||||||
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8' }) {
|
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8' }) {
|
||||||
checkMessage(message); publicKeys = toArray(publicKeys);
|
checkMessage(message); publicKeys = toArray(publicKeys);
|
||||||
|
|
||||||
if (asyncProxy) { // use web worker if available
|
if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||||
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format });
|
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format });
|
||||||
}
|
}
|
||||||
|
|
||||||
return execute(() => {
|
return message.decrypt(privateKey, sessionKey, password).then(message => {
|
||||||
|
|
||||||
message = message.decrypt(privateKey, sessionKey, password);
|
|
||||||
const result = parseMessage(message, format);
|
const result = parseMessage(message, format);
|
||||||
if (publicKeys && result.data) { // verify only if publicKeys are specified
|
if (publicKeys && result.data) { // verify only if publicKeys are specified
|
||||||
result.signatures = message.verify(publicKeys);
|
result.signatures = message.verify(publicKeys);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}, 'Error decrypting message');
|
}).catch(onError.bind(null, 'Error decrypting message'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import * as packets from './all_packets.js'; // re-import module to parse packet
|
||||||
export { default as Compressed } from './compressed.js';
|
export { default as Compressed } from './compressed.js';
|
||||||
/** @see module:packet/sym_encrypted_integrity_protected */
|
/** @see module:packet/sym_encrypted_integrity_protected */
|
||||||
export { default as SymEncryptedIntegrityProtected } from './sym_encrypted_integrity_protected.js';
|
export { default as SymEncryptedIntegrityProtected } from './sym_encrypted_integrity_protected.js';
|
||||||
|
/** @see module:packet/sym_encrypted_aead_protected */
|
||||||
|
export { default as SymEncryptedAEADProtected } from './sym_encrypted_aead_protected.js';
|
||||||
/** @see module:packet/public_key_encrypted_session_key */
|
/** @see module:packet/public_key_encrypted_session_key */
|
||||||
export { default as PublicKeyEncryptedSessionKey } from './public_key_encrypted_session_key.js';
|
export { default as PublicKeyEncryptedSessionKey } from './public_key_encrypted_session_key.js';
|
||||||
/** @see module:packet/sym_encrypted_session_key */
|
/** @see module:packet/sym_encrypted_session_key */
|
||||||
|
|
66
src/packet/sym_encrypted_aead_protected.js
Normal file
66
src/packet/sym_encrypted_aead_protected.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// OpenPGP.js - An OpenPGP implementation in javascript
|
||||||
|
// Copyright (C) 2016 Tankred Hase
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
|
// License as published by the Free Software Foundation; either
|
||||||
|
// version 3.0 of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the Symmetrically Encrypted AEAD Protected Data Packet <br/>
|
||||||
|
* <br/>
|
||||||
|
* {@link https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1}: AEAD Protected Data Packet
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import util from '../util.js';
|
||||||
|
import crypto from '../crypto';
|
||||||
|
import enums from '../enums.js';
|
||||||
|
|
||||||
|
const IV_LEN = crypto.gcm.ivLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export default function SymEncryptedAEADProtected() {
|
||||||
|
this.tag = enums.packet.symEncryptedAEADProtected;
|
||||||
|
this.iv = null;
|
||||||
|
this.encrypted = null;
|
||||||
|
/** Decrypted packets contained within.
|
||||||
|
* @type {module:packet/packetlist} */
|
||||||
|
this.packets = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymEncryptedAEADProtected.prototype.read = function (bytes) {
|
||||||
|
this.iv = bytes.subarray(0, IV_LEN);
|
||||||
|
this.encrypted = bytes.subarray(IV_LEN, bytes.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
SymEncryptedAEADProtected.prototype.write = function () {
|
||||||
|
return util.concatUint8Array([this.iv, this.encrypted]);
|
||||||
|
};
|
||||||
|
|
||||||
|
SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) {
|
||||||
|
return crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv).then(decrypted => {
|
||||||
|
this.packets.read(decrypted);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) {
|
||||||
|
var data = this.packets.write();
|
||||||
|
this.iv = crypto.random.getRandomValues(new Uint8Array(IV_LEN));
|
||||||
|
|
||||||
|
return crypto.gcm.encrypt(sessionKeyAlgorithm, data, key, this.iv).then(encrypted => {
|
||||||
|
this.encrypted = encrypted;
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user