UNPKG

ecies-geth

Version:

JavaScript Elliptic Curve Integrated Encryption Scheme (ECIES) Library - Based off Geth's implementation

321 lines (317 loc) 15.3 kB
"use strict"; /* MIT License Copyright (c) 2019 Cyril Dever Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decrypt = exports.encrypt = exports.derive = exports.verify = exports.sign = exports.getPublic = exports.kdf = void 0; /** * Browser ecies-geth implementation. * This is based off the `eccrypto` JS module. */ var elliptic_1 = require("elliptic"); /* eslint-disable @typescript-eslint/unbound-method */ var ec = new elliptic_1.ec('secp256k1'); /* eslint-disable @typescript-eslint/strict-boolean-expressions */ var crypto = window.crypto || window.msCrypto; // eslint-disable-line @typescript-eslint/no-non-null-assertion var subtle = crypto.subtle || crypto.webkitSubtle; /* eslint-enable @typescript-eslint/strict-boolean-expressions */ if (subtle === undefined || crypto === undefined) { throw new Error('crypto and/or subtle api unavailable'); } // Use the browser RNG var randomBytes = function (size) { return crypto.getRandomValues(Buffer.alloc(size)); }; // Get the browser SHA256 implementation var sha256 = function (msg) { return subtle.digest({ name: 'SHA-256' }, msg).then(Buffer.from); }; // The KDF as implemented in Parity mimics Geth's implementation var kdf = function (secret, outputLength) { var ctr = 1; var written = 0; var willBeResult = Promise.resolve(Buffer.from('')); var _loop_1 = function () { var ctrs = Buffer.from([ctr >> 24, ctr >> 16, ctr >> 8, ctr]); var willBeHashResult = sha256(Buffer.concat([ctrs, secret])); willBeResult = willBeResult.then(function (result) { return willBeHashResult.then(function (hashResult) { return Buffer.concat([result, hashResult]); }); }); written += 32; ctr += 1; }; while (written < outputLength) { _loop_1(); } return willBeResult; }; exports.kdf = kdf; var aesCtrEncrypt = function (counter, key, data) { return subtle .importKey('raw', key, 'AES-CTR', false, ['encrypt']) .then(function (cryptoKey) { return subtle.encrypt({ name: 'AES-CTR', counter: counter, length: 128 }, cryptoKey, data); }).then(Buffer.from); }; var aesCtrDecrypt = function (counter, key, data) { return subtle .importKey('raw', key, 'AES-CTR', false, ['decrypt']) .then(function (cryptoKey) { return subtle.decrypt({ name: 'AES-CTR', counter: counter, length: 128 }, cryptoKey, data); }).then(Buffer.from); }; var hmacSha256Sign = function (key, msg) { var algorithm = { name: 'HMAC', hash: { name: 'SHA-256' } }; return subtle.importKey('raw', key, algorithm, false, ['sign']) .then(function (cryptoKey) { return subtle.sign(algorithm, cryptoKey, msg); }) .then(Buffer.from); }; var hmacSha256Verify = function (key, msg, sig) { var algorithm = { name: 'HMAC', hash: { name: 'SHA-256' } }; var keyp = subtle.importKey('raw', key, algorithm, false, ['verify']); return keyp.then(function (cryptoKey) { return subtle.verify(algorithm, cryptoKey, sig, msg); }); }; /** * Compute the public key for a given private key. * * @param {Buffer} privateKey - A 32-byte private key * @return {Promise<Buffer>} A promise that resolve with the 65-byte public key or reject on wrong private key. * @function */ var getPublic = function (privateKey) { return new Promise(function (resolve, reject) { if (privateKey.length !== 32) { reject(new Error('Private key should be 32 bytes long')); } else { resolve(Buffer.from(ec.keyFromPrivate(privateKey).getPublic('array'))); } }); }; exports.getPublic = getPublic; /** * Create an ECDSA signature. * * @param {Buffer} privateKey - A 32-byte private key * @param {Buffer} msg - The message being signed, no more than 32 bytes * @return {Promise.<Buffer>} A promise that resolves with the signature and rejects on bad key or message */ var sign = function (privateKey, msg) { return new Promise(function (resolve, reject) { if (privateKey.length !== 32) { reject(new Error('Private key should be 32 bytes long')); } else if (msg.length <= 0) { reject(new Error('Message should not be empty')); } else if (msg.length > 32) { reject(new Error('Message is too long (max 32 bytes)')); } else { resolve(Buffer.from(ec.sign(msg, privateKey, { canonical: true }).toDER('hex'), 'hex')); } }); }; exports.sign = sign; /** * Verify an ECDSA signature. * * @param {Buffer} publicKey - A 65-byte public key * @param {Buffer} msg - The message being verified * @param {Buffer} sig - The signature * @return {Promise.<true>} A promise that resolves on correct signature and rejects on bad key or signature */ var verify = function (publicKey, msg, sig) { return new Promise(function (resolve, reject) { try { if (publicKey.length !== 65 || publicKey[0] !== 4) { reject(new Error('Public key should 65 bytes long')); } else if (msg.length <= 0) { reject(new Error('Message should not be empty')); } else if (msg.length > 32) { reject(new Error('Message is too long (max 32 bytes)')); } else if (!ec.verify(msg, sig.toString('hex'), publicKey, 'hex')) { // eslint-disable-line @typescript-eslint/no-unsafe-argument reject(new Error('Bad signature')); } else { resolve(true); } } catch (_) { // eslint-disable-line @typescript-eslint/no-unused-vars reject(new Error('Invalid arguments')); } }); }; exports.verify = verify; /** * Derive shared secret for given private and public keys. * * @param {Buffer} privateKey - Sender's private key (32 bytes) * @param {Buffer} publicKey - Recipient's public key (65 bytes) * @return {Promise.<Buffer>} A promise that resolves with the derived shared secret (Px, 32 bytes) and rejects on bad key */ var derive = function (privateKeyA, publicKeyB) { return new Promise(function (resolve, reject) { if (privateKeyA.length !== 32) { reject(new Error("Bad private key, it should be 32 bytes but it's actually ".concat(privateKeyA.length, " bytes long"))); } else if (publicKeyB.length !== 65) { reject(new Error("Bad public key, it should be 65 bytes but it's actually ".concat(publicKeyB.length, " bytes long"))); } else if (publicKeyB[0] !== 4) { reject(new Error('Bad public key, a valid public key would begin with 4')); } else { var keyA = ec.keyFromPrivate(privateKeyA); var keyB = ec.keyFromPublic(publicKeyB); var Px = keyA.derive(keyB.getPublic()); // BN instance resolve(pad32(Buffer.from(Px.toArray()))); } }); }; exports.derive = derive; /** * Encrypt message for given recepient's public key. * * @param {Buffer} publicKeyTo - Recipient's public key (65 bytes) * @param {Buffer} msg - The message being encrypted * @param {?{?iv: Buffer, ?ephemPrivateKey: Buffer}} opts - You may also specify initialization vector (16 bytes) and ephemeral private key (32 bytes) to get deterministic results. * @return {Promise.<Buffer>} - A promise that resolves with the ECIES structure serialized */ var encrypt = function (publicKeyTo, msg, opts) { return __awaiter(void 0, void 0, void 0, function () { var ephemPrivateKey; return __generator(this, function (_a) { opts = opts || {}; ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); return [2 /*return*/, (0, exports.derive)(ephemPrivateKey, publicKeyTo) .then(function (sharedPx) { return (0, exports.kdf)(sharedPx, 32); }) .then(function (hash) { return __awaiter(void 0, void 0, void 0, function () { var iv, encryptionKey; return __generator(this, function (_a) { iv = opts.iv || randomBytes(16); encryptionKey = hash.slice(0, 16); return [2 /*return*/, aesCtrEncrypt(iv, encryptionKey, msg) .then(function (cipherText) { return Buffer.concat([iv, cipherText]); }) .then(function (ivCipherText) { return sha256(hash.slice(16)) .then(function (macKey) { return hmacSha256Sign(macKey, ivCipherText); }) .then(function (HMAC) { return (0, exports.getPublic)(ephemPrivateKey) .then(function (ephemPublicKey) { return Buffer.concat([ephemPublicKey, ivCipherText, HMAC]); }); }); })]; }); }); })]; }); }); }; exports.encrypt = encrypt; var metaLength = 1 + 64 + 16 + 32; /** * Decrypt message using given private key. * * @param {Buffer} privateKey - A 32-byte private key of recepient of the message * @param {Ecies} encrypted - ECIES serialized structure (result of ECIES encryption) * @return {Promise.<Buffer>} - A promise that resolves with the plaintext on successful decryption and rejects on failure */ var decrypt = function (privateKey, encrypted) { return new Promise(function (resolve, reject) { if (encrypted.length <= metaLength) { reject(new Error("Invalid Ciphertext. Data is too small. It should ba at least ".concat(metaLength, " bytes"))); } else if (encrypted[0] !== 4) { reject(new Error("Not a valid ciphertext. It should begin with 4 but actually begin with ".concat(encrypted[0]))); } else { // deserialize var ephemPublicKey = encrypted.slice(0, 65); var cipherTextLength = encrypted.length - metaLength; var iv_1 = encrypted.slice(65, 65 + 16); var cipherAndIv_1 = encrypted.slice(65, 65 + 16 + cipherTextLength); var ciphertext_1 = cipherAndIv_1.slice(16); var msgMac_1 = encrypted.slice(65 + 16 + cipherTextLength); // check HMAC resolve((0, exports.derive)(privateKey, ephemPublicKey) .then(function (px) { return (0, exports.kdf)(px, 32); }) .then(function (hash) { return sha256(hash.slice(16)).then(function (macKey) { return [hash.slice(0, 16), macKey]; }); }) .then(function (_a) { var encryptionKey = _a[0], macKey = _a[1]; return hmacSha256Verify(macKey, cipherAndIv_1, msgMac_1) .then(function (isHmacGood) { return !isHmacGood ? Promise.reject(new Error('Incorrect MAC')) : aesCtrDecrypt(iv_1, encryptionKey, ciphertext_1); }); }).then(Buffer.from)); } }); }; exports.decrypt = decrypt; var pad32 = function (msg) { if (msg.length < 32) { var buff = Buffer.alloc(32).fill(0); msg.copy(buff, 32 - msg.length); return buff; } else return msg; }; __exportStar(require("./model"), exports); /* eslint-enable @typescript-eslint/unbound-method */ //# sourceMappingURL=browser.js.map