Skip to content

Commit

Permalink
chore(node/crypto): add public-encrypt (#1982)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt3k authored Mar 7, 2022
1 parent 6212f24 commit 8cb37f7
Show file tree
Hide file tree
Showing 27 changed files with 697 additions and 0 deletions.
1 change: 1 addition & 0 deletions node/_crypto/crypto_browserify/browserify_rsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { BN } from "./bn.js/bn.js";
import { randomBytes } from "./randombytes.ts";
import { Buffer } from "../../buffer.ts";

function blind(priv) {
const r = getr(priv);
Expand Down
22 changes: 22 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/mgf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// Copyright 2017 Calvin Metcalf. All rights reserved. MIT license.

import { createHash } from "../../hash.ts";
import { Buffer } from "../../../buffer.ts";

export default function (seed, len) {
let t = Buffer.alloc(0);
let i = 0;
let c;
while (t.length < len) {
c = i2ops(i++);
t = Buffer.concat([t, createHash("sha1").update(seed).update(c).digest()]);
}
return t.slice(0, len);
}

function i2ops(c) {
const out = Buffer.allocUnsafe(4);
out.writeUInt32BE(c, 0);
return out;
}
15 changes: 15 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// Copyright 2017 Calvin Metcalf. All rights reserved. MIT license.

import { publicEncrypt } from "./public_encrypt.js";
import { privateDecrypt } from "./private_decrypt.js";

export { privateDecrypt, publicEncrypt };

export function privateEncrypt(key, buf) {
return publicEncrypt(key, buf, true);
}

export function publicDecrypt(key, buf) {
return privateDecrypt(key, buf, true);
}
111 changes: 111 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/private_decrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// Copyright 2017 Calvin Metcalf. All rights reserved. MIT license.

import parseKeys from "../parse_asn1/mod.js";
import { createHash } from "../../hash.ts";
import mgf from "./mgf.js";
import { xor } from "./xor.js";
import { BN } from "../bn.js/bn.js";
import { withPublic } from "./with_public.js";
import crt from "../browserify_rsa.js";
import { Buffer } from "../../../buffer.ts";

export function privateDecrypt(privateKey, enc, reverse) {
let padding;
if (privateKey.padding) {
padding = privateKey.padding;
} else if (reverse) {
padding = 1;
} else {
padding = 4;
}

const key = parseKeys(privateKey);
const k = key.modulus.byteLength();
if (enc.length > k || new BN(enc).cmp(key.modulus) >= 0) {
throw new Error("decryption error");
}
let msg;
if (reverse) {
msg = withPublic(new BN(enc), key);
} else {
msg = crt(enc, key);
}
const zBuffer = Buffer.alloc(k - msg.length);
msg = Buffer.concat([zBuffer, msg], k);
if (padding === 4) {
return oaep(key, msg);
} else if (padding === 1) {
return pkcs1(key, msg, reverse);
} else if (padding === 3) {
return msg;
} else {
throw new Error("unknown padding");
}
}

function oaep(key, msg) {
const k = key.modulus.byteLength();
const iHash = createHash("sha1").update(Buffer.alloc(0)).digest();
const hLen = iHash.length;
if (msg[0] !== 0) {
throw new Error("decryption error");
}
const maskedSeed = msg.slice(1, hLen + 1);
const maskedDb = msg.slice(hLen + 1);
const seed = xor(maskedSeed, mgf(maskedDb, hLen));
const db = xor(maskedDb, mgf(seed, k - hLen - 1));
if (compare(iHash, db.slice(0, hLen))) {
throw new Error("decryption error");
}
let i = hLen;
while (db[i] === 0) {
i++;
}
if (db[i++] !== 1) {
throw new Error("decryption error");
}
return db.slice(i);
}

function pkcs1(_key, msg, reverse) {
const p1 = msg.slice(0, 2);
let i = 2;
let status = 0;
while (msg[i++] !== 0) {
if (i >= msg.length) {
status++;
break;
}
}
const ps = msg.slice(2, i - 1);

if (
(p1.toString("hex") !== "0002" && !reverse) ||
(p1.toString("hex") !== "0001" && reverse)
) {
status++;
}
if (ps.length < 8) {
status++;
}
if (status) {
throw new Error("decryption error");
}
return msg.slice(i);
}
function compare(a, b) {
a = Buffer.from(a);
b = Buffer.from(b);
let dif = 0;
let len = a.length;
if (a.length !== b.length) {
dif++;
len = Math.min(a.length, b.length);
}
let i = -1;
while (++i < len) {
dif += a[i] ^ b[i];
}
return dif;
}
104 changes: 104 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/public_encrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// Copyright 2017 Calvin Metcalf. All rights reserved. MIT license.

import parseKeys from "../parse_asn1/mod.js";
import { randomBytes } from "../randombytes.ts";
import { createHash } from "../../hash.ts";
import mgf from "./mgf.js";
import { xor } from "./xor.js";
import { BN } from "../bn.js/bn.js";
import { withPublic } from "./with_public.js";
import crt from "../browserify_rsa.js";
import { Buffer } from "../../../buffer.ts";

export function publicEncrypt(publicKey, msg, reverse) {
let padding;
if (publicKey.padding) {
padding = publicKey.padding;
} else if (reverse) {
padding = 1;
} else {
padding = 4;
}
const key = parseKeys(publicKey);
let paddedMsg;
if (padding === 4) {
paddedMsg = oaep(key, msg);
} else if (padding === 1) {
paddedMsg = pkcs1(key, msg, reverse);
} else if (padding === 3) {
paddedMsg = new BN(msg);
if (paddedMsg.cmp(key.modulus) >= 0) {
throw new Error("data too long for modulus");
}
} else {
throw new Error("unknown padding");
}
if (reverse) {
return crt(paddedMsg, key);
} else {
return withPublic(paddedMsg, key);
}
}

function oaep(key, msg) {
const k = key.modulus.byteLength();
const mLen = msg.length;
const iHash = createHash("sha1").update(Buffer.alloc(0)).digest();
const hLen = iHash.length;
const hLen2 = 2 * hLen;
if (mLen > k - hLen2 - 2) {
throw new Error("message too long");
}
const ps = Buffer.alloc(k - mLen - hLen2 - 2);
const dblen = k - hLen - 1;
const seed = randomBytes(hLen);
const maskedDb = xor(
Buffer.concat([iHash, ps, Buffer.alloc(1, 1), msg], dblen),
mgf(seed, dblen),
);
const maskedSeed = xor(seed, mgf(maskedDb, hLen));
return new BN(Buffer.concat([Buffer.alloc(1), maskedSeed, maskedDb], k));
}
function pkcs1(key, msg, reverse) {
const mLen = msg.length;
const k = key.modulus.byteLength();
if (mLen > k - 11) {
throw new Error("message too long");
}
let ps;
if (reverse) {
ps = Buffer.alloc(k - mLen - 3, 0xff);
} else {
ps = nonZero(k - mLen - 3);
}
return new BN(
Buffer.concat([
Buffer.from([
0,
reverse ? 1 : 2,
]),
ps,
Buffer.alloc(1),
msg,
], k),
);
}
function nonZero(len) {
const out = Buffer.allocUnsafe(len);
let i = 0;
let cache = randomBytes(len * 2);
let cur = 0;
let num;
while (i < len) {
if (cur === cache.length) {
cache = randomBytes(len * 2);
cur = 0;
}
num = cache[cur++];
if (num) {
out[i++] = num;
}
}
return out;
}
16 changes: 16 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/1024.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKulUTZ8B1qccZ8c
DXRGSY08gW8KvLlcxxxGC4gZHNT3CBUF8n5R4KE30aZyYZ/rtsQZu05juZJxaJ0q
mbe75dlQ5d+Xc9BMXeQg/MpTZw5TAN7OIdGYYpFBe+1PLZ6wEfjkYrMqMUcfq2Lq
hTLdAbvBJnuRcYZLqmBeOQ8FTrKrAgMBAAECgYEAnkHRbEPU3/WISSQrP36iyCb2
S/SBZwKkzmvCrBxDWhPeDswp9c/2JY76rNWfLzy8iXgUG8WUzvHje61Qh3gmBcKe
bUaTGl4Vy8Ha1YBADo5RfRrdm0FE4tvgvu/TkqFqpBBZweu54285hk5zlG7n/D7Y
dnNXUpu5MlNb5x3gW0kCQQDUL//cwcXUxY/evaJP4jSe+ZwEQZo+zXRLiPUulBoV
aw28CVMuxdgwqAo1X1IKefPeUaf7RQu8gCKaRnpGuEuXAkEAzxZTfMmvmCUDIew4
5Gk6bK265XQWdhcgiq254lpBGOYmDj9yCE7yA+zmASQwMsXTdQOi1hOCEyrXuSJ5
c++EDQJAFh3WrnzoEPByuYXMmET8tSFRWMQ5vpgNqh3haHR5b4gUC2hxaiunCBNL
1RpVY9AoUiDywGcG/SPh93CnKB3niwJBAKP7AtsifZgVXtiizB4aMThTjVYaSZrz
D0Kg9DuHylpkDChmFu77TGrNUQgAVuYtfhb/bRblVa/F0hJ4eQHT3JUCQBVT68tb
OgRUk0aP9tC3021VN82X6+klowSQN8oBPX8+TfDWSUilp/+j24Hky+Z29Do7yR/R
qutnL92CvBlVLV4=
-----END PRIVATE KEY-----
6 changes: 6 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/1024.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrpVE2fAdanHGfHA10RkmNPIFv
Cry5XMccRguIGRzU9wgVBfJ+UeChN9GmcmGf67bEGbtOY7mScWidKpm3u+XZUOXf
l3PQTF3kIPzKU2cOUwDeziHRmGKRQXvtTy2esBH45GKzKjFHH6ti6oUy3QG7wSZ7
kXGGS6pgXjkPBU6yqwIDAQAB
-----END PUBLIC KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAi9LqZQx4JFXAICCAAw
HQYJYIZIAWUDBAECBBA+js1fG4Rv/yRN7oZvxbgyBIGQ/D4yj86M1x8lMsnAHQ/K
7/ryb/baDNHqN9LTZanEGBuyxgrTzt08SiL+h91yFGMoaly029K1VgEI8Lxu5Np/
A+LK7ewh73ABzsbuxYdcXI+rKnrvLN9Tt6veDs4GlqTTsWwq5wF0C+6gaYRBXA74
T1b6NykGh2UNL5U5pHZEYdOVLz+lRJL7gYqlweNHP/S3
-----END ENCRYPTED PRIVATE KEY-----
5 changes: 5 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/ec.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIDF6Xv8Sv//wGUWD+c780ppGrU0QdZWCAzxAQPQX8r/uoAcGBSuBBAAK
oUQDQgAEIZeowDylls4K/wfBjO18bYo7gGx8nYQRija4e/qEMikOHJai7geeUreU
r5Xky/Ax7s2dGtegsPNsPgGe5MpQvg==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/ec.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEIZeowDylls4K/wfBjO18bYo7gGx8nYQR
ija4e/qEMikOHJai7geeUreUr5Xky/Ax7s2dGtegsPNsPgGe5MpQvg==
-----END PUBLIC KEY-----
60 changes: 60 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/node_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// Copyright 2017 Calvin Metcalf. All rights reserved. MIT license.

import * as crypto from "../mod.js";
import fs from "../../../../fs.ts";
import path from "../../../../path.ts";
import { Buffer } from "../../../../buffer.ts";
import { assertEquals, assertThrows } from "../../../../../testing/asserts.ts";

// Test RSA encryption/decryption
Deno.test("node tests", function () {
const keyPem = fs.readFileSync(
path.fromFileUrl(new URL("test_key.pem", import.meta.url)),
"ascii",
);
const rsaPubPem = fs.readFileSync(
path.fromFileUrl(new URL("test_rsa_pubkey.pem", import.meta.url)),
"ascii",
);
const rsaKeyPem = fs.readFileSync(
path.fromFileUrl(new URL("test_rsa_privkey.pem", import.meta.url)),
"ascii",
);
const rsaKeyPemEncrypted = fs.readFileSync(
path.fromFileUrl(
new URL("test_rsa_privkey_encrypted.pem", import.meta.url),
),
"ascii",
);
const input = "I AM THE WALRUS";
const bufferToEncrypt = Buffer.from(input);

let encryptedBuffer = crypto.publicEncrypt(rsaPubPem, bufferToEncrypt);

let decryptedBuffer = crypto.privateDecrypt(rsaKeyPem, encryptedBuffer);
assertEquals(input, decryptedBuffer.toString());

const decryptedBufferWithPassword = crypto.privateDecrypt({
key: rsaKeyPemEncrypted,
passphrase: "password",
}, encryptedBuffer);
assertEquals(input, decryptedBufferWithPassword.toString());

encryptedBuffer = crypto.publicEncrypt(keyPem, bufferToEncrypt);

decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
assertEquals(input, decryptedBuffer.toString());

encryptedBuffer = crypto.privateEncrypt(keyPem, bufferToEncrypt);

decryptedBuffer = crypto.publicDecrypt(keyPem, encryptedBuffer);
assertEquals(input, decryptedBuffer.toString());

assertThrows(function () {
crypto.privateDecrypt({
key: rsaKeyPemEncrypted,
passphrase: "wrong",
}, encryptedBuffer);
});
});
18 changes: 18 additions & 0 deletions node/_crypto/crypto_browserify/public_encrypt/test/pass.1024.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIji3ZZ6JbsA4CAggA
MB0GCWCGSAFlAwQBFgQQC6MKblq8zyX90/KmgotsMQSCAoDghNf+yxPC/KRh7F3O
k0lMgtDkV+wCLDv7aBvUqy8Ry2zqFPIlfLb8XtSW943XEu6KUI13IZPEr8p9h1ve
Iye6L0g6uAgbFxBE2DwBBSI7mYr7lokr4v0k+inMKf4JeRdI9XWgwOILKTGf1vH7
PhvBnqLhOg6BIOuF426qpiyYlmRda74d0Th4o6ZyhyMSzPI1XbWSg719Ew3N/tLe
OHdYl0eFrgNjq+xO4Ev+W7eNIh/XBMQtk9wo+mxeNdldRnX822HxTsL8fSSPs+9T
W5M/2EBTJMSsswSjZyFkq8ehtxovI2u0IBX1IiPulyUZLnSNPDV1eUVClK6rk+q1
kVsfJhUr2qvIjNlQWlbEXQj4VwGtgl0++l8vdpj59MuN2J3Nx5TNMLjA6BYAa/tr
Bu928QoT7ET+SGx5XKCwKb5fwXmDlV5zZC4kZWTaF/d/Icvj5F+fDZuYFg1JOXNZ
+q2oA1qMYaHGX6lF3pbO84ebg1iwQTDM8iIqFeSMGUJTnk/3a7sqfaWQbEQwGb+X
fXnSTwkF+wO2rriPbFvWyzecWu67zDCP0ZWUgGb86sSJCM7xRGShESwCjOrb88F1
5SZjyIqogrkc3IWiLH9gc5U8d86qoFjJnP6BfwYks1UIyXNGKfZTCqICpMphV+IS
b0N2jprjLTkWR6nxYGSH1bkKMs7x1M0FBLWWLAZqPn9X3pe6JwIBds04O6XjF0un
oxwDjcJdoxVs7PgRiM5d1Tubqu2zmpCCmXNiqi9B0+rV9/jHg9IA5gUfvYdCcEv+
oAr90I+2+PuBFa9lgdbDV6DtZk4bSYluqamxVeLPg/vrewYfVfDv6jftfY1D0DEy
69H0
-----END ENCRYPTED PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSK/7i5BV0x+gmX16Wrm7kRkCZ
y1QUt6wiM2g+SAZTYR0381VnSMX2cv7CpN3499lZj1rL5S7YTaZZwX3RvU5fz56/
eDX6ciL/PZsbclN2KdkMWYgmcb9J1zUeoMQ3cjfFUCdQZ/ZvDWa+wY2Zg8os2Bow
AoufHtYHm3eOly/cWwIDAQAB
-----END PUBLIC KEY-----
Loading

0 comments on commit 8cb37f7

Please sign in to comment.