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