mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-21 15:26:16 -04:00
Merge 6272d0e809
into 7c8be12d52
This commit is contained in:
commit
9a05d8d396
14 changed files with 104 additions and 85 deletions
|
@ -24,8 +24,8 @@ class AESDecrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -103,9 +103,9 @@ class AESDecrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*
|
*
|
||||||
* @throws {OperationError} if cannot decrypt input or invalid key length
|
* @throws {OperationError} if cannot decrypt input or invalid key length
|
||||||
*/
|
*/
|
||||||
|
@ -128,6 +128,7 @@ The following algorithms will be used based on the size of the key:
|
||||||
32 bytes = AES-256`);
|
32 bytes = AES-256`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
||||||
|
@ -148,7 +149,8 @@ The following algorithms will be used based on the size of the key:
|
||||||
const result = decipher.finish();
|
const result = decipher.finish();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
const output = outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
} else {
|
} else {
|
||||||
throw new OperationError("Unable to decrypt input with these parameters.");
|
throw new OperationError("Unable to decrypt input with these parameters.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class AESEncrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -97,9 +97,9 @@ class AESEncrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*
|
*
|
||||||
* @throws {OperationError} if invalid key length
|
* @throws {OperationError} if invalid key length
|
||||||
*/
|
*/
|
||||||
|
@ -121,6 +121,7 @@ The following algorithms will be used based on the size of the key:
|
||||||
32 bytes = AES-256`);
|
32 bytes = AES-256`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
// Handle NoPadding modes
|
// Handle NoPadding modes
|
||||||
|
@ -140,19 +141,23 @@ The following algorithms will be used based on the size of the key:
|
||||||
cipher.update(forge.util.createBuffer(input));
|
cipher.update(forge.util.createBuffer(input));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
|
let output;
|
||||||
if (outputType === "Hex") {
|
if (outputType === "Hex") {
|
||||||
if (mode === "GCM") {
|
if (mode === "GCM") {
|
||||||
return cipher.output.toHex() + "\n\n" +
|
output = cipher.output.toHex() + "\n\n" +
|
||||||
"Tag: " + cipher.mode.tag.toHex();
|
"Tag: " + cipher.mode.tag.toHex();
|
||||||
|
} else {
|
||||||
|
output = cipher.output.toHex();
|
||||||
}
|
}
|
||||||
return cipher.output.toHex();
|
|
||||||
} else {
|
} else {
|
||||||
if (mode === "GCM") {
|
if (mode === "GCM") {
|
||||||
return cipher.output.getBytes() + "\n\n" +
|
output = cipher.output.getBytes() + "\n\n" +
|
||||||
"Tag: " + cipher.mode.tag.getBytes();
|
"Tag: " + cipher.mode.tag.getBytes();
|
||||||
|
} else {
|
||||||
|
output = cipher.output.getBytes();
|
||||||
}
|
}
|
||||||
return cipher.output.getBytes();
|
|
||||||
}
|
}
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ class AESKeyUnwrap extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.";
|
this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key (KEK)",
|
"name": "Key (KEK)",
|
||||||
|
@ -54,9 +54,9 @@ class AESKeyUnwrap extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -70,6 +70,7 @@ class AESKeyUnwrap extends Operation {
|
||||||
if (iv.length !== 8) {
|
if (iv.length !== 8) {
|
||||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||||
}
|
}
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
const inputData = Utils.convertToByteString(input, inputType);
|
const inputData = Utils.convertToByteString(input, inputType);
|
||||||
if (inputData.length % 8 !== 0 || inputData.length < 24) {
|
if (inputData.length % 8 !== 0 || inputData.length < 24) {
|
||||||
throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)");
|
throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)");
|
||||||
|
@ -117,10 +118,8 @@ class AESKeyUnwrap extends Operation {
|
||||||
}
|
}
|
||||||
const P = R.join("");
|
const P = R.join("");
|
||||||
|
|
||||||
if (outputType === "Hex") {
|
const output = outputType === "Hex" ? toHexFast(Utils.strToArrayBuffer(P)) : P;
|
||||||
return toHexFast(Utils.strToArrayBuffer(P));
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
|
||||||
return P;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ class AESKeyWrap extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.";
|
this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key (KEK)",
|
"name": "Key (KEK)",
|
||||||
|
@ -54,9 +54,9 @@ class AESKeyWrap extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -70,6 +70,7 @@ class AESKeyWrap extends Operation {
|
||||||
if (iv.length !== 8) {
|
if (iv.length !== 8) {
|
||||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||||
}
|
}
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
const inputData = Utils.convertToByteString(input, inputType);
|
const inputData = Utils.convertToByteString(input, inputType);
|
||||||
if (inputData.length % 8 !== 0 || inputData.length < 16) {
|
if (inputData.length % 8 !== 0 || inputData.length < 16) {
|
||||||
throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)");
|
throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)");
|
||||||
|
@ -104,10 +105,8 @@ class AESKeyWrap extends Operation {
|
||||||
}
|
}
|
||||||
const C = A + R.join("");
|
const C = A + R.join("");
|
||||||
|
|
||||||
if (outputType === "Hex") {
|
const output = outputType === "Hex" ? toHexFast(Utils.strToArrayBuffer(C)) : C;
|
||||||
return toHexFast(Utils.strToArrayBuffer(C));
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
|
||||||
return C;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ class BlowfishDecrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -59,9 +59,9 @@ class BlowfishDecrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -80,6 +80,7 @@ Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = Blowfish.createDecipher(key, mode);
|
const decipher = Blowfish.createDecipher(key, mode);
|
||||||
|
@ -88,7 +89,8 @@ Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
const result = decipher.finish();
|
const result = decipher.finish();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
const output = outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
} else {
|
} else {
|
||||||
throw new OperationError("Unable to decrypt input with these parameters.");
|
throw new OperationError("Unable to decrypt input with these parameters.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ class BlowfishEncrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -59,9 +59,9 @@ class BlowfishEncrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -80,6 +80,7 @@ Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const cipher = Blowfish.createCipher(key, mode);
|
const cipher = Blowfish.createCipher(key, mode);
|
||||||
|
@ -87,11 +88,8 @@ Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
cipher.update(forge.util.createBuffer(input));
|
cipher.update(forge.util.createBuffer(input));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
if (outputType === "Hex") {
|
const output = outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
||||||
return cipher.output.toHex();
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
} else {
|
|
||||||
return cipher.output.getBytes();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class DESDecrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -58,9 +58,9 @@ class DESDecrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -81,6 +81,7 @@ DES uses an IV length of 8 bytes (64 bits).
|
||||||
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
|
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
|
||||||
|
@ -97,7 +98,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
const result = decipher.finish();
|
const result = decipher.finish();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
const output = outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
} else {
|
} else {
|
||||||
throw new OperationError("Unable to decrypt input with these parameters.");
|
throw new OperationError("Unable to decrypt input with these parameters.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class DESEncrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -58,9 +58,9 @@ class DESEncrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -79,6 +79,7 @@ DES uses an IV length of 8 bytes (64 bits).
|
||||||
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const cipher = forge.cipher.createCipher("DES-" + mode, key);
|
const cipher = forge.cipher.createCipher("DES-" + mode, key);
|
||||||
|
@ -86,7 +87,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
cipher.update(forge.util.createBuffer(input));
|
cipher.update(forge.util.createBuffer(input));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
const output = outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ class RC2Decrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/RC2";
|
this.infoURL = "https://wikipedia.org/wiki/RC2";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -52,9 +52,9 @@ class RC2Decrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -62,13 +62,15 @@ class RC2Decrypt extends Operation {
|
||||||
[,, inputType, outputType] = args,
|
[,, inputType, outputType] = args,
|
||||||
decipher = forge.rc2.createDecryptionCipher(key);
|
decipher = forge.rc2.createDecryptionCipher(key);
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
decipher.start(iv || null);
|
decipher.start(iv || null);
|
||||||
decipher.update(forge.util.createBuffer(input));
|
decipher.update(forge.util.createBuffer(input));
|
||||||
decipher.finish();
|
decipher.finish();
|
||||||
|
|
||||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
const output = outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class RC2Encrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/RC2";
|
this.infoURL = "https://wikipedia.org/wiki/RC2";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -53,9 +53,9 @@ class RC2Encrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -63,13 +63,15 @@ class RC2Encrypt extends Operation {
|
||||||
[,, inputType, outputType] = args,
|
[,, inputType, outputType] = args,
|
||||||
cipher = forge.rc2.createEncryptionCipher(key);
|
cipher = forge.rc2.createEncryptionCipher(key);
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
cipher.start(iv || null);
|
cipher.start(iv || null);
|
||||||
cipher.update(forge.util.createBuffer(input));
|
cipher.update(forge.util.createBuffer(input));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
const output = outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class Rabbit extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++";
|
this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)";
|
this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -58,9 +58,9 @@ class Rabbit extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
@ -214,12 +214,14 @@ class Rabbit extends Operation {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = Utils.convertToByteString(input, inputType);
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
|
const dataString = Utils.convertToByteString(input, inputType);
|
||||||
|
const data = Array.from(dataString).map((c) => c.charCodeAt(0));
|
||||||
const result = new Uint8Array(data.length);
|
const result = new Uint8Array(data.length);
|
||||||
for (let i = 0; i <= data.length - 16; i += 16) {
|
for (let i = 0; i <= data.length - 16; i += 16) {
|
||||||
extract();
|
extract();
|
||||||
for (let j = 0; j < 16; j++) {
|
for (let j = 0; j < 16; j++) {
|
||||||
result[i + j] = data.charCodeAt(i + j) ^ S[j];
|
result[i + j] = data[i + j] ^ S[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.length % 16 !== 0) {
|
if (data.length % 16 !== 0) {
|
||||||
|
@ -228,18 +230,18 @@ class Rabbit extends Operation {
|
||||||
extract();
|
extract();
|
||||||
if (littleEndian) {
|
if (littleEndian) {
|
||||||
for (let j = 0; j < length; j++) {
|
for (let j = 0; j < length; j++) {
|
||||||
result[offset + j] = data.charCodeAt(offset + j) ^ S[j];
|
result[offset + j] = data[offset + j] ^ S[j];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let j = 0; j < length; j++) {
|
for (let j = 0; j < length; j++) {
|
||||||
result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j];
|
result[offset + j] = data[offset + j] ^ S[16 - length + j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outputType === "Hex") {
|
if (outputType === "Hex") {
|
||||||
return toHexFast(result);
|
return Array.from(toHexFast(result)).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
return Utils.byteArrayToChars(result);
|
return Array.from(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class TripleDESDecrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -58,9 +58,9 @@ class TripleDESDecrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -82,6 +82,7 @@ Triple DES uses an IV length of 8 bytes (64 bits).
|
||||||
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = forge.cipher.createDecipher("3DES-" + mode,
|
const decipher = forge.cipher.createDecipher("3DES-" + mode,
|
||||||
|
@ -99,7 +100,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
const result = decipher.finish();
|
const result = decipher.finish();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
const output = outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
} else {
|
} else {
|
||||||
throw new OperationError("Unable to decrypt input with these parameters.");
|
throw new OperationError("Unable to decrypt input with these parameters.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class TripleDESEncrypt extends Operation {
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "byteArray";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
|
@ -58,9 +58,9 @@ class TripleDESEncrypt extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {byteArray}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
@ -81,6 +81,7 @@ Triple DES uses an IV length of 8 bytes (64 bits).
|
||||||
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input = input.map((c) => String.fromCharCode(c)).join("");
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const cipher = forge.cipher.createCipher("3DES-" + mode,
|
const cipher = forge.cipher.createCipher("3DES-" + mode,
|
||||||
|
@ -89,7 +90,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
||||||
cipher.update(forge.util.createBuffer(input));
|
cipher.update(forge.util.createBuffer(input));
|
||||||
cipher.finish();
|
cipher.finish();
|
||||||
|
|
||||||
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
const output = outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
|
||||||
|
return Array.from(output).map((c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,8 +117,8 @@ TestRegister.addApiTests([
|
||||||
const result = chef.help("tripleDESDecrypt");
|
const result = chef.help("tripleDESDecrypt");
|
||||||
assert.strictEqual(result[0].name, "Triple DES Decrypt");
|
assert.strictEqual(result[0].name, "Triple DES Decrypt");
|
||||||
assert.strictEqual(result[0].module, "Ciphers");
|
assert.strictEqual(result[0].module, "Ciphers");
|
||||||
assert.strictEqual(result[0].inputType, "string");
|
assert.strictEqual(result[0].inputType, "byteArray");
|
||||||
assert.strictEqual(result[0].outputType, "string");
|
assert.strictEqual(result[0].outputType, "byteArray");
|
||||||
assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.");
|
assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.");
|
||||||
assert.strictEqual(result[0].args.length, 5);
|
assert.strictEqual(result[0].args.length, 5);
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue