cipher.js
6.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
* Cipher base API.
*
* @author Dave Longley
*
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
*/
var forge = require('./forge');
require('./util');
module.exports = forge.cipher = forge.cipher || {};
// registered algorithms
forge.cipher.algorithms = forge.cipher.algorithms || {};
/**
* Creates a cipher object that can be used to encrypt data using the given
* algorithm and key. The algorithm may be provided as a string value for a
* previously registered algorithm or it may be given as a cipher algorithm
* API object.
*
* @param algorithm the algorithm to use, either a string or an algorithm API
* object.
* @param key the key to use, as a binary-encoded string of bytes or a
* byte buffer.
*
* @return the cipher.
*/
forge.cipher.createCipher = function(algorithm, key) {
var api = algorithm;
if(typeof api === 'string') {
api = forge.cipher.getAlgorithm(api);
if(api) {
api = api();
}
}
if(!api) {
throw new Error('Unsupported algorithm: ' + algorithm);
}
// assume block cipher
return new forge.cipher.BlockCipher({
algorithm: api,
key: key,
decrypt: false
});
};
/**
* Creates a decipher object that can be used to decrypt data using the given
* algorithm and key. The algorithm may be provided as a string value for a
* previously registered algorithm or it may be given as a cipher algorithm
* API object.
*
* @param algorithm the algorithm to use, either a string or an algorithm API
* object.
* @param key the key to use, as a binary-encoded string of bytes or a
* byte buffer.
*
* @return the cipher.
*/
forge.cipher.createDecipher = function(algorithm, key) {
var api = algorithm;
if(typeof api === 'string') {
api = forge.cipher.getAlgorithm(api);
if(api) {
api = api();
}
}
if(!api) {
throw new Error('Unsupported algorithm: ' + algorithm);
}
// assume block cipher
return new forge.cipher.BlockCipher({
algorithm: api,
key: key,
decrypt: true
});
};
/**
* Registers an algorithm by name. If the name was already registered, the
* algorithm API object will be overwritten.
*
* @param name the name of the algorithm.
* @param algorithm the algorithm API object.
*/
forge.cipher.registerAlgorithm = function(name, algorithm) {
name = name.toUpperCase();
forge.cipher.algorithms[name] = algorithm;
};
/**
* Gets a registered algorithm by name.
*
* @param name the name of the algorithm.
*
* @return the algorithm, if found, null if not.
*/
forge.cipher.getAlgorithm = function(name) {
name = name.toUpperCase();
if(name in forge.cipher.algorithms) {
return forge.cipher.algorithms[name];
}
return null;
};
var BlockCipher = forge.cipher.BlockCipher = function(options) {
this.algorithm = options.algorithm;
this.mode = this.algorithm.mode;
this.blockSize = this.mode.blockSize;
this._finish = false;
this._input = null;
this.output = null;
this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
this._decrypt = options.decrypt;
this.algorithm.initialize(options);
};
/**
* Starts or restarts the encryption or decryption process, whichever
* was previously configured.
*
* For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
* of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
* bytes, then it must be Nb (16) bytes in length. If the IV is given in as
* 32-bit integers, then it must be 4 integers long.
*
* Note: an IV is not required or used in ECB mode.
*
* For GCM-mode, the IV must be given as a binary-encoded string of bytes or
* a byte buffer. The number of bytes should be 12 (96 bits) as recommended
* by NIST SP-800-38D but another length may be given.
*
* @param options the options to use:
* iv the initialization vector to use as a binary-encoded string of
* bytes, null to reuse the last ciphered block from a previous
* update() (this "residue" method is for legacy support only).
* additionalData additional authentication data as a binary-encoded
* string of bytes, for 'GCM' mode, (default: none).
* tagLength desired length of authentication tag, in bits, for
* 'GCM' mode (0-128, default: 128).
* tag the authentication tag to check if decrypting, as a
* binary-encoded string of bytes.
* output the output the buffer to write to, null to create one.
*/
BlockCipher.prototype.start = function(options) {
options = options || {};
var opts = {};
for(var key in options) {
opts[key] = options[key];
}
opts.decrypt = this._decrypt;
this._finish = false;
this._input = forge.util.createBuffer();
this.output = options.output || forge.util.createBuffer();
this.mode.start(opts);
};
/**
* Updates the next block according to the cipher mode.
*
* @param input the buffer to read from.
*/
BlockCipher.prototype.update = function(input) {
if(input) {
// input given, so empty it into the input buffer
this._input.putBuffer(input);
}
// do cipher operation until it needs more input and not finished
while(!this._op.call(this.mode, this._input, this.output, this._finish) &&
!this._finish) {}
// free consumed memory from input buffer
this._input.compact();
};
/**
* Finishes encrypting or decrypting.
*
* @param pad a padding function to use in CBC mode, null for default,
* signature(blockSize, buffer, decrypt).
*
* @return true if successful, false on error.
*/
BlockCipher.prototype.finish = function(pad) {
// backwards-compatibility w/deprecated padding API
// Note: will overwrite padding functions even after another start() call
if(pad && (this.mode.name === 'ECB' || this.mode.name === 'CBC')) {
this.mode.pad = function(input) {
return pad(this.blockSize, input, false);
};
this.mode.unpad = function(output) {
return pad(this.blockSize, output, true);
};
}
// build options for padding and afterFinish functions
var options = {};
options.decrypt = this._decrypt;
// get # of bytes that won't fill a block
options.overflow = this._input.length() % this.blockSize;
if(!this._decrypt && this.mode.pad) {
if(!this.mode.pad(this._input, options)) {
return false;
}
}
// do final update
this._finish = true;
this.update();
if(this._decrypt && this.mode.unpad) {
if(!this.mode.unpad(this.output, options)) {
return false;
}
}
if(this.mode.afterFinish) {
if(!this.mode.afterFinish(this.output, options)) {
return false;
}
}
return true;
};