Skip to content

Commit 6e34774

Browse files
committed
2 parents 2e2dcfa + b0c9a18 commit 6e34774

File tree

4 files changed

+320
-0
lines changed

4 files changed

+320
-0
lines changed

src/core/config/Categories.json

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"Substitute",
122122
"Derive PBKDF2 key",
123123
"Derive EVP key",
124+
"Derive HKDF key",
124125
"Bcrypt",
125126
"Scrypt",
126127
"JWT Sign",

src/core/operations/DeriveHKDFKey.mjs

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* @author mikecat
3+
* @copyright Crown Copyright 2023
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import Utils from "../Utils.mjs";
9+
import OperationError from "../errors/OperationError.mjs";
10+
import CryptoApi from "crypto-api/src/crypto-api.mjs";
11+
12+
/**
13+
* Derive HKDF Key operation
14+
*/
15+
class DeriveHKDFKey extends Operation {
16+
17+
/**
18+
* DeriveHKDFKey constructor
19+
*/
20+
constructor() {
21+
super();
22+
23+
this.name = "Derive HKDF Key";
24+
this.module = "Crypto";
25+
this.description = "A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869.";
26+
this.infoURL = "https://wikipedia.org/wiki/HKDF";
27+
this.inputType = "ArrayBuffer";
28+
this.outputType = "string";
29+
this.args = [
30+
{
31+
"name": "Salt",
32+
"type": "toggleString",
33+
"value": "",
34+
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
35+
},
36+
{
37+
"name": "Info",
38+
"type": "toggleString",
39+
"value": "",
40+
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
41+
},
42+
{
43+
"name": "Hashing function",
44+
"type": "option",
45+
"value": [
46+
"MD2",
47+
"MD4",
48+
"MD5",
49+
"SHA0",
50+
"SHA1",
51+
"SHA224",
52+
"SHA256",
53+
"SHA384",
54+
"SHA512",
55+
"SHA512/224",
56+
"SHA512/256",
57+
"RIPEMD128",
58+
"RIPEMD160",
59+
"RIPEMD256",
60+
"RIPEMD320",
61+
"HAS160",
62+
"Whirlpool",
63+
"Whirlpool-0",
64+
"Whirlpool-T",
65+
"Snefru"
66+
],
67+
"defaultIndex": 6
68+
},
69+
{
70+
"name": "Extract mode",
71+
"type": "argSelector",
72+
"value": [
73+
{
74+
"name": "with salt",
75+
"on": [0]
76+
},
77+
{
78+
"name": "no salt",
79+
"off": [0]
80+
},
81+
{
82+
"name": "skip",
83+
"off": [0]
84+
}
85+
]
86+
},
87+
{
88+
"name": "L (number of output octets)",
89+
"type": "number",
90+
"value": 16,
91+
"min": 0
92+
},
93+
];
94+
}
95+
96+
/**
97+
* @param {ArrayBuffer} input
98+
* @param {Object[]} args
99+
* @returns {ArrayBuffer}
100+
*/
101+
run(input, args) {
102+
const argSalt = Utils.convertToByteString(args[0].string || "", args[0].option),
103+
info = Utils.convertToByteString(args[1].string || "", args[1].option),
104+
hashFunc = args[2].toLowerCase(),
105+
extractMode = args[3],
106+
L = args[4],
107+
IKM = Utils.arrayBufferToStr(input, false),
108+
hasher = CryptoApi.getHasher(hashFunc),
109+
HashLen = hasher.finalize().length;
110+
111+
if (L < 0) {
112+
throw new OperationError("L must be non-negative");
113+
}
114+
if (L > 255 * HashLen) {
115+
throw new OperationError("L too large (maximum length for " + args[2] + " is " + (255 * HashLen) + ")");
116+
}
117+
118+
const hmacHash = function(key, data) {
119+
hasher.reset();
120+
const mac = CryptoApi.getHmac(key, hasher);
121+
mac.update(data);
122+
return mac.finalize();
123+
};
124+
const salt = extractMode === "with salt" ? argSalt : "\0".repeat(HashLen);
125+
const PRK = extractMode === "skip" ? IKM : hmacHash(salt, IKM);
126+
let T = "";
127+
let result = "";
128+
for (let i = 1; i <= 255 && result.length < L; i++) {
129+
const TNext = hmacHash(PRK, T + info + String.fromCharCode(i));
130+
result += TNext;
131+
T = TNext;
132+
}
133+
return CryptoApi.encoder.toHex(result.substring(0, L));
134+
}
135+
136+
}
137+
138+
export default DeriveHKDFKey;

tests/operations/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import "./tests/AESKeyWrap.mjs";
132132
import "./tests/Rabbit.mjs";
133133
import "./tests/LevenshteinDistance.mjs";
134134
import "./tests/SwapCase.mjs";
135+
import "./tests/HKDF.mjs";
135136

136137
// Cannot test operations that use the File type yet
137138
// import "./tests/SplitColourChannels.mjs";

tests/operations/tests/HKDF.mjs

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @author mikecat
3+
* @copyright Crown Copyright 2023
4+
* @license Apache-2.0
5+
*/
6+
import TestRegister from "../../lib/TestRegister.mjs";
7+
8+
TestRegister.addTests([
9+
{
10+
"name": "HKDF: RFC5869 Test Case 1",
11+
"input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
12+
"expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
13+
"recipeConfig": [
14+
{
15+
"op": "From Hex",
16+
"args": ["None"],
17+
},
18+
{
19+
"op": "Derive HKDF Key",
20+
"args": [
21+
{"option": "Hex", "string": "000102030405060708090a0b0c"},
22+
{"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"},
23+
"SHA256", "with salt", 42,
24+
],
25+
},
26+
],
27+
},
28+
{
29+
"name": "HKDF: RFC5869 Test Case 2",
30+
"input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f",
31+
"expectedOutput": "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87",
32+
"recipeConfig": [
33+
{
34+
"op": "From Hex",
35+
"args": ["None"],
36+
},
37+
{
38+
"op": "Derive HKDF Key",
39+
"args": [
40+
{"option": "Hex", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"},
41+
{"option": "Hex", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"},
42+
"SHA256", "with salt", 82,
43+
],
44+
},
45+
],
46+
},
47+
{
48+
"name": "HKDF: RFC5869 Test Case 3",
49+
"input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
50+
"expectedOutput": "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8",
51+
"recipeConfig": [
52+
{
53+
"op": "From Hex",
54+
"args": ["None"],
55+
},
56+
{
57+
"op": "Derive HKDF Key",
58+
"args": [
59+
{"option": "Hex", "string": ""},
60+
{"option": "Hex", "string": ""},
61+
"SHA256", "with salt", 42,
62+
],
63+
},
64+
],
65+
},
66+
{
67+
"name": "HKDF: RFC5869 Test Case 4",
68+
"input": "0b0b0b0b0b0b0b0b0b0b0b",
69+
"expectedOutput": "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896",
70+
"recipeConfig": [
71+
{
72+
"op": "From Hex",
73+
"args": ["None"],
74+
},
75+
{
76+
"op": "Derive HKDF Key",
77+
"args": [
78+
{"option": "Hex", "string": "000102030405060708090a0b0c"},
79+
{"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"},
80+
"SHA1", "with salt", 42,
81+
],
82+
},
83+
],
84+
},
85+
{
86+
"name": "HKDF: RFC5869 Test Case 5",
87+
"input": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f",
88+
"expectedOutput": "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4",
89+
"recipeConfig": [
90+
{
91+
"op": "From Hex",
92+
"args": ["None"],
93+
},
94+
{
95+
"op": "Derive HKDF Key",
96+
"args": [
97+
{"option": "Hex", "string": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"},
98+
{"option": "Hex", "string": "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"},
99+
"SHA1", "with salt", 82,
100+
],
101+
},
102+
],
103+
},
104+
{
105+
"name": "HKDF: RFC5869 Test Case 6",
106+
"input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
107+
"expectedOutput": "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918",
108+
"recipeConfig": [
109+
{
110+
"op": "From Hex",
111+
"args": ["None"],
112+
},
113+
{
114+
"op": "Derive HKDF Key",
115+
"args": [
116+
{"option": "Hex", "string": ""},
117+
{"option": "Hex", "string": ""},
118+
"SHA1", "with salt", 42,
119+
],
120+
},
121+
],
122+
},
123+
{
124+
"name": "HKDF: RFC5869 Test Case 7",
125+
"input": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
126+
"expectedOutput": "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48",
127+
"recipeConfig": [
128+
{
129+
"op": "From Hex",
130+
"args": ["None"],
131+
},
132+
{
133+
"op": "Derive HKDF Key",
134+
"args": [
135+
{"option": "Hex", "string": ""},
136+
{"option": "Hex", "string": ""},
137+
"SHA1", "no salt", 42,
138+
],
139+
},
140+
],
141+
},
142+
{
143+
"name": "HKDF: RFC5869 Test Case 1 with skip extract",
144+
"input": "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5",
145+
"expectedOutput": "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
146+
"recipeConfig": [
147+
{
148+
"op": "From Hex",
149+
"args": ["None"],
150+
},
151+
{
152+
"op": "Derive HKDF Key",
153+
"args": [
154+
{"option": "Hex", "string": ""},
155+
{"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"},
156+
"SHA256", "skip", 42,
157+
],
158+
},
159+
],
160+
},
161+
{
162+
"name": "HKDF: too large L",
163+
"input": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
164+
"expectedOutput": "L too large (maximum length for SHA256 is 8160)",
165+
"recipeConfig": [
166+
{
167+
"op": "From Hex",
168+
"args": ["None"],
169+
},
170+
{
171+
"op": "Derive HKDF Key",
172+
"args": [
173+
{"option": "Hex", "string": "000102030405060708090a0b0c"},
174+
{"option": "Hex", "string": "f0f1f2f3f4f5f6f7f8f9"},
175+
"SHA256", "with salt", 8161,
176+
],
177+
},
178+
],
179+
},
180+
]);

0 commit comments

Comments
 (0)