Added two operations RSADecryptPublic and RSAEncryptPrivate using nodes RSA tooling.

This commit is contained in:
Fra3zz 2026-06-15 10:49:28 -05:00
parent cc38f3a53f
commit 7a5d4d2736
5 changed files with 392 additions and 0 deletions

View File

@ -199,6 +199,8 @@
"RSA Verify", "RSA Verify",
"RSA Encrypt", "RSA Encrypt",
"RSA Decrypt", "RSA Decrypt",
"RSA Encrypt Private Key",
"RSA Decrypt Public Key",
"Generate ECDSA Key Pair", "Generate ECDSA Key Pair",
"ECDSA Signature Conversion", "ECDSA Signature Conversion",
"ECDSA Sign", "ECDSA Sign",

View File

@ -0,0 +1,79 @@
/**
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
/**
* RSA Decrypt Public Key operation
*/
class RSADecryptPublic extends Operation {
/**
* RSADecryptPublic constructor
*/
constructor() {
super();
this.name = "RSA Decrypt Public Key";
this.module = "Ciphers";
this.description = "Decrypt a message that was encrypted with an RSA private key, using the corresponding public key. Supports PKCS#1 (<code>BEGIN RSA PUBLIC KEY</code>) and X.509 SubjectPublicKeyInfo (<code>BEGIN PUBLIC KEY</code>) formats.<br><br>This is the complement of the <b>RSA Encrypt Private Key</b> operation. Standard RSA decryption uses a private key; this operation uses the public key to reverse a private-key-encrypted ciphertext.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Public Key (PEM)",
type: "text",
value: "-----BEGIN PUBLIC KEY-----"
},
{
name: "Encryption Scheme",
type: "option",
value: ["PKCS1 v1.5", "RAW"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, scheme] = args;
if (!pemKey.startsWith("-----BEGIN")) {
throw new OperationError("Please enter a public key.");
}
let pubKey;
try {
pubKey = forge.pki.publicKeyFromPem(pemKey);
} catch (err) {
throw new OperationError(`Unable to load public key: ${err.message}`);
}
try {
if (scheme === "PKCS1 v1.5") {
const decrypted = forge.pki.rsa.decrypt(input, pubKey, true, true);
return forge.util.decodeUtf8(decrypted);
} else {
return forge.pki.rsa.decrypt(input, pubKey, true, false);
}
} catch (err) {
if (err.message === "Encrypted message length is invalid.") {
throw new OperationError(`Input length (${err.length} bytes) does not match key size (${err.expected} bytes). Ensure the input is raw ciphertext, not base64 or hex encoded.`);
}
throw new OperationError(err);
}
}
}
export default RSADecryptPublic;

View File

@ -0,0 +1,90 @@
/**
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
/**
* RSA Encrypt Private Key operation
*/
class RSAEncryptPrivate extends Operation {
/**
* RSAEncryptPrivate constructor
*/
constructor() {
super();
this.name = "RSA Encrypt Private Key";
this.module = "Ciphers";
this.description = "Encrypt a message with a PEM encoded RSA private key using the private key transform. Supports PKCS#1 and PKCS#8 key formats, including password-protected keys. The resulting ciphertext can be decrypted by anyone holding the corresponding public key.<br><br>Note: standard RSA encryption uses a public key. This operation applies the private key transform (equivalent to signing without hashing) and is sometimes required for compatibility with specific systems.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Private Key (PEM)",
type: "text",
value: "-----BEGIN RSA PRIVATE KEY-----"
},
{
name: "Key Password",
type: "text",
value: ""
},
{
name: "Encryption Scheme",
type: "option",
value: ["PKCS1 v1.5", "RAW"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, password, scheme] = args;
if (!pemKey.startsWith("-----BEGIN")) {
throw new OperationError("Please enter a private key.");
}
let privKey;
try {
privKey = forge.pki.decryptRsaPrivateKey(pemKey, password);
} catch (err) {}
if (!privKey) {
try {
privKey = forge.pki.privateKeyFromPem(pemKey);
} catch (err) {
throw new OperationError(`Unable to load private key: ${err.message}`);
}
}
if (!privKey) {
throw new OperationError("Unable to load private key. Check the key format and password.");
}
try {
const plaintextBytes = forge.util.encodeUtf8(input);
const bt = scheme === "PKCS1 v1.5" ? 0x01 : false;
return forge.pki.rsa.encrypt(plaintextBytes, privKey, bt);
} catch (err) {
if (err.message && err.message.includes("too long")) {
throw new OperationError(`Message is too long for this key size.`);
}
throw new OperationError(err);
}
}
}
export default RSAEncryptPrivate;

View File

@ -0,0 +1,102 @@
/**
* RSA Decrypt Public Key tests.
*
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const PRIV_512_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL
NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF
F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL
WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p
6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf
RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw
NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=
-----END RSA PRIVATE KEY-----`;
const PUB_512_SPKI = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`;
const PUB_512_PKCS1 = `-----BEGIN RSA PUBLIC KEY-----
MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB
757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE=
-----END RSA PUBLIC KEY-----`;
TestRegister.addTests([
{
name: "RSA Decrypt Public Key: missing key",
input: "anything",
expectedOutput: "Please enter a public key.",
recipeConfig: [
{
op: "RSA Decrypt Public Key",
args: ["", "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, SPKI public key, round-trip Hello World",
input: "Hello World",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, PKCS#1 RSA public key, round-trip Hello World",
input: "Hello World",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_PKCS1, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, SPKI public key, round-trip empty",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, decrypt from base64 ciphertext",
input: "2EoJzsG9F9el3Dwxm+7Ua1Ykdpnh9WeS2prrkAYGfnkN9irgRvNIUkOSUJAgy4yQ5RjqAHkKgAdwHvjWqLuLhA==",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "From Base64",
args: ["A-Za-z0-9+/=", true, false]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
}
]);

View File

@ -0,0 +1,119 @@
/**
* RSA Encrypt Private Key tests.
*
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const PRIV_512_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL
NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF
F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL
WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p
6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf
RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw
NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=
-----END RSA PRIVATE KEY-----`;
const PRIV_512_PKCS8 = `-----BEGIN PRIVATE KEY-----
MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp
ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx
2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB
vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox
/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM
dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3
J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe
JzOq+X832g==
-----END PRIVATE KEY-----`;
const PRIV_512_PKCS8_ENC = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBrzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQILP5/Mw4jnpUCAggA
MB0GCWCGSAFlAwQBAgQQ18VGFlqTKWNfEro31Ev+UgSCAWAwara+xKGKPSNGOakz
DLST6Xhnxv6PcL1+Ei4RR65ns94ofPJl/2lai8R5itkL9gRbqiGi1L7rdwsP8Awb
O5d4KUnVMSEFTBDyIBU5w5Di4WJFU04PuchVHDRBiqYNHVEu0y5JmQu0cPpaTd/m
eNuLaymoIduUFLj/aMpda0BTEOwdCJ+G4P5IOwAXkGQ4JUX3NPp3S/bwmS6HYnct
W3q65mhEIuJb1CK7O1k97L9ArmFQ+p74jELvwfJTaP9FNnee3Rs1hpbQ3AVwtEtq
3w0P2XXEDDVcwNvNh+sLKhp7WYWvQM9NXnWPQFu1FiJqIfUk6/SMdplH0KEAdexb
XvERV5TRWqb/ilQPYYiJVgQpe0evZblvERMS3Ul3hSrsEYcr4MBII+RvMZHss/hQ
2yp5XcQNzP9uceT4hIGAlvTAG4BGaH2tbU4x/T9ae0mx52/8I8RS9gSgPqK2SdSf
tMER
-----END ENCRYPTED PRIVATE KEY-----`;
const HELLO_WORLD_PKCS1_B64 = "2EoJzsG9F9el3Dwxm+7Ua1Ykdpnh9WeS2prrkAYGfnkN9irgRvNIUkOSUJAgy4yQ5RjqAHkKgAdwHvjWqLuLhA==";
const EMPTY_PKCS1_B64 = "rfhNp4omqGuP+7J6Z2MuMeDwBzGcRm9kFIPPAn/S6HPXfXN/4Kasd1FUdr9n0+7egspZNSPg5TWq4GKGXuhBhA==";
TestRegister.addTests([
{
name: "RSA Encrypt Private Key: missing key",
input: "Hello World",
expectedOutput: "Please enter a private key.",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: ["", "", "PKCS1 v1.5"]
}
]
},
{
name: "RSA Encrypt Private Key: PKCS#1 key, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: PKCS#8 key, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS8, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: encrypted PKCS#8 key with password, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS8_ENC, "testpassword", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: empty input",
input: "",
expectedOutput: EMPTY_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
}
]);