Support AES-GCM with AEAD Protected Data Packets

Closes openpgpjs/openpgpjs#421
This commit is contained in:
Tankred Hase 2016-03-22 02:41:10 +08:00
parent c9b20c96e0
commit ded8926b27
9 changed files with 213 additions and 25 deletions

View File

@ -37,6 +37,7 @@ export default {
prefer_hash_algorithm: enums.hash.sha256,
encryption_cipher: enums.symmetric.aes256,
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
ignore_mdc_error: false, // fail on decrypt if message is not integrity protected
rsa_blinding: true,

105
src/crypto/gcm.js Normal file
View 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));
}
}

View File

@ -8,6 +8,7 @@
import cipher from './cipher';
import hash from './hash';
import cfb from './cfb';
import * as gcm from './gcm';
import publicKey from './public_key';
import signature from './signature';
import random from './random';
@ -21,6 +22,8 @@ const mod = {
hash: hash,
/** @see module:crypto/cfb */
cfb: cfb,
/** @see module:crypto/aes-gcm */
gcm: gcm,
/** @see module:crypto/public_key */
publicKey: publicKey,
/** @see module:crypto/signature */

View File

@ -91,6 +91,7 @@ export default {
} else {
throw new Error('No secure random number generator available.');
}
return buf;
},
/**

View File

@ -94,7 +94,8 @@ export default {
publicSubkey: 14,
userAttribute: 17,
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

View File

@ -96,15 +96,23 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) {
if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
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) {
var symEncryptedPacket = symEncryptedPacketlist[0];
symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data);
return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => {
var resultMsg = new Message(symEncryptedPacket.packets);
// remove packets after decryption
symEncryptedPacket.packets = new packet.List();
return resultMsg;
});
}
return Promise.resolve();
};
/**
@ -219,18 +227,21 @@ Message.prototype.encrypt = function(keys, passwords) {
var packetlist = msg.packets;
var symEncryptedPacket;
if (config.integrity_protect) {
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
} else if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
} else {
symEncryptedPacket = new packet.SymmetricallyEncrypted();
}
symEncryptedPacket.packets = this.packets;
symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey);
return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey).then(() => {
packetlist.push(symEncryptedPacket);
// remove packets after encryption
symEncryptedPacket.packets = new packet.List();
return msg;
});
};
/**

View File

@ -169,17 +169,16 @@ export function decryptKey({ privateKey, passphrase }) {
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) {
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 execute(() => {
let message = createMessage(data, filename);
if (privateKeys) { // sign the message only if private keys are specified
message = message.sign(privateKeys);
}
message = message.encrypt(publicKeys, passwords);
return message.encrypt(publicKeys, passwords).then(message => {
if(armor) {
return {
@ -190,7 +189,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
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' }) {
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 execute(() => {
return message.decrypt(privateKey, sessionKey, password).then(message => {
message = message.decrypt(privateKey, sessionKey, password);
const result = parseMessage(message, format);
if (publicKeys && result.data) { // verify only if publicKeys are specified
result.signatures = message.verify(publicKeys);
}
return result;
}, 'Error decrypting message');
}).catch(onError.bind(null, 'Error decrypting message'));
}

View File

@ -12,6 +12,8 @@ import * as packets from './all_packets.js'; // re-import module to parse packet
export { default as Compressed } from './compressed.js';
/** @see module:packet/sym_encrypted_integrity_protected */
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 */
export { default as PublicKeyEncryptedSessionKey } from './public_key_encrypted_session_key.js';
/** @see module:packet/sym_encrypted_session_key */

View 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;
});
};