1 | nodeCrypto = require('crypto')
|
2 | sjcl = require('../libs/sjcl')
|
3 |
|
4 | # Constants
|
5 | BLOCKSIZE = 16
|
6 |
|
7 |
|
8 | ###*
|
9 | * @class Crypto
|
10 | ###
|
11 |
|
12 | Crypto =
|
13 |
|
14 | ###*
|
15 | * Encipher data using AES256 in CBC mode
|
16 | * @param {Buffer} plaintext The data to encrypt.
|
17 | * @param {String|Buffer} key The key to encrypt with. Can be a Buffer or
|
18 | * hex encoded string.
|
19 | * @param {String|Buffer} iv The IV. Can be a Buffer or hex encoded string.
|
20 | * @param {string} [encoding=Buffer] The format to return the encrypted
|
21 | * data at.
|
22 | * @return {Buffer|String} The encrypted data.
|
23 | ###
|
24 | encrypt: (plaintext, key, iv, encoding) ->
|
25 | iv = @toBuffer(iv)
|
26 | key = @toBuffer(key)
|
27 | cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv)
|
28 | # Don't use the default padding
|
29 | cipher.setAutoPadding(false)
|
30 | binary = cipher.update(plaintext) + cipher.final()
|
31 | buffer = new Buffer(binary, 'binary')
|
32 | if encoding?
|
33 | return buffer.toString(encoding)
|
34 | else
|
35 | return buffer
|
36 |
|
37 |
|
38 | ###*
|
39 | * Decipher encrypted data using AES256 in CBC mode
|
40 | * @param {String|Buffer} ciphertext The data to decipher. Must be a
|
41 | * multiple of the blocksize.
|
42 | * @param {String|Buffer} key The key to decipher the data with.
|
43 | * @param {String|Buffer} iv The initialization vector to use.
|
44 | * @param {String} [encoding=Buffer] The format to return the decrypted
|
45 | * contents as.
|
46 | * @return {Buffer|String} The decrypted contents.
|
47 | ###
|
48 | decrypt: (ciphertext, key, iv, encoding) ->
|
49 | iv = @toBuffer(iv)
|
50 | key = @toBuffer(key)
|
51 | ciphertext = @toBuffer(ciphertext)
|
52 | cipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv)
|
53 | # Don't use the default padding
|
54 | cipher.setAutoPadding(false)
|
55 | binary = cipher.update(ciphertext) + cipher.final()
|
56 | buffer = new Buffer(binary, 'binary')
|
57 | if encoding?
|
58 | return buffer.toString(encoding)
|
59 | else
|
60 | return buffer
|
61 |
|
62 |
|
63 | ###*
|
64 | * Generate keys from password using PKDF2-HMAC-SHA512.
|
65 | * @param {String} password The password.
|
66 | * @param {String|Buffer} salt The salt.
|
67 | * @param {Number} [iterations=10000] The numbers of iterations.
|
68 | * @param {Numbers} [keysize=512] The length of the derived key in bits.
|
69 | * @return {String} Returns the derived key encoded as hex.
|
70 | ###
|
71 | pbkdf2: (password, salt, iterations=10000, keysize=512) ->
|
72 |
|
73 | # Users SJCL PBKDF2 with Node.js Crypto HMAC-SHA512
|
74 | # Because Node.js Crypto PBKDF2 only supports HMAC-SHA1
|
75 |
|
76 | shaKey = "sha#{keysize}"
|
77 |
|
78 | class hmac
|
79 |
|
80 | constructor: (key) ->
|
81 | @key = sjcl.codec.utf8String.fromBits(key)
|
82 |
|
83 | encrypt: (sjclArray) ->
|
84 | byteArray = sjcl.codec.bytes.fromBits(sjclArray)
|
85 | buffer = new Buffer(byteArray)
|
86 | hex = nodeCrypto.createHmac(shaKey, @key).update(buffer).digest('hex')
|
87 | bits = sjcl.codec.hex.toBits(hex)
|
88 | return bits
|
89 |
|
90 | salt = sjcl.codec.hex.toBits(@toHex(salt))
|
91 | bits = sjcl.misc.pbkdf2(password, salt, iterations, keysize, hmac)
|
92 | return sjcl.codec.hex.fromBits(bits)
|
93 |
|
94 |
|
95 | ###*
|
96 | * Cryptographically hash data using HMAC.
|
97 | * @param {String|Buffer} data The data to be hashed.
|
98 | * @param {String|Buffer} key The key to use with HMAC.
|
99 | * @param {string} mode The type of hash to use, such as sha1, sha256 or
|
100 | * sha512.
|
101 | * @return {String} The hmac digest encoded as hex.
|
102 | ###
|
103 | hmac: (data, key, keysize) ->
|
104 | data = @toBuffer(data)
|
105 | key = @toBuffer(key)
|
106 | mode = "sha#{keysize}"
|
107 | hmac = nodeCrypto.createHmac(mode, key)
|
108 | hmac.update(data)
|
109 | return hmac.digest('hex')
|
110 |
|
111 |
|
112 | ###*
|
113 | * Create a hash digest of data.
|
114 | * @param {String|Buffer} data The data to hash.
|
115 | * @param {String} mode The type of hash to use, such as sha1, sha256, or
|
116 | * sha512.
|
117 | * @return {String} The hash digest encoded as hex.
|
118 | ###
|
119 | hash: (data, keysize) ->
|
120 | data = @toBuffer(data)
|
121 | mode = "sha#{keysize}"
|
122 | hash = nodeCrypto.createHash(mode)
|
123 | hash.update(data)
|
124 | return hash.digest('hex')
|
125 |
|
126 |
|
127 | ###*
|
128 | * Prepend padding to data to make it fill the blocksize.
|
129 | * @param {Buffer} data The data to pad.
|
130 | * @return {Buffer} The data with padding added.
|
131 | ###
|
132 | pad: (data) ->
|
133 | bytesToPad = BLOCKSIZE - (data.length % BLOCKSIZE)
|
134 | padding = @randomBytes(bytesToPad)
|
135 | return Buffer.concat([padding, data])
|
136 |
|
137 |
|
138 | ###*
|
139 | * Remove padding from text.
|
140 | * @param {Numbers} plaintextLength The length of the plaintext in bytes.
|
141 | * @param {String|Buffer} data The data to remove the padding as a string
|
142 | * encoded as hex or a buffer.
|
143 | * @return {String} The data with the padding removed encoded as hex.
|
144 | ###
|
145 | unpad: (plaintextLength, data) ->
|
146 | data = @toHex(data)
|
147 | # One byte uses two hex characters
|
148 | plaintextLength *= 2
|
149 | return data[-plaintextLength..]
|
150 |
|
151 |
|
152 | ###*
|
153 | * Generates cryptographically strong pseudo-random data.
|
154 | * @param {Numbers} length How many bytes of data you want.
|
155 | * @return {Buffer} The random data as a Buffer.
|
156 | ###
|
157 | randomBytes: (length) ->
|
158 | return nodeCrypto.randomBytes(length)
|
159 |
|
160 |
|
161 | ###*
|
162 | * Convert data to a Buffer
|
163 | * @param {String|Buffer} data The data to be converted. If a string, must
|
164 | * be encoded as hex.
|
165 | * @param {String} [encoding=hex] The format of the data to convert.
|
166 | * @return {Buffer} The data as a Buffer
|
167 | ###
|
168 | toBuffer: (data, encoding='hex') ->
|
169 | if data instanceof Buffer then return data
|
170 | return new Buffer(data, encoding)
|
171 |
|
172 |
|
173 | ###*
|
174 | * Convert data to hex.
|
175 | * @param {String|Buffer} data The data to be converted.
|
176 | * @return {String} The data encoded as hex.
|
177 | ###
|
178 | toHex: (data) ->
|
179 | if data instanceof Buffer then return data.toString('hex')
|
180 | return data
|
181 |
|
182 |
|
183 | ###*
|
184 | * Convert base64 to Buffer.
|
185 | * @param {String} data A base64 encoded string.
|
186 | * @return {Buffer} The base64 string as a Buffer.
|
187 | ###
|
188 | fromBase64: (data) ->
|
189 | return new Buffer(data, 'base64')
|
190 |
|
191 |
|
192 | ###*
|
193 | * Join an array of buffers together.
|
194 | * @param {Array} buffers An array of buffers.
|
195 | * @return {Buffer} The buffers joined together.
|
196 | ###
|
197 | concat: (buffers) ->
|
198 | Buffer.concat(buffers)
|
199 |
|
200 |
|
201 | ###*
|
202 | * Parse a litte endian number.
|
203 | * @author Jim Rogers {@link http://www.jimandkatrin.com/CodeBlog/post/Parse-a-little-endian.aspx}
|
204 | * @param {String} hex The little endian number.
|
205 | * @return {Number} The little endian converted to a number.
|
206 | ###
|
207 | parseLittleEndian: (hex) ->
|
208 | result = 0
|
209 | pow = 0
|
210 | while hex.length > 0
|
211 | result += parseInt(hex.substring(0, 2), 16) * Math.pow(2, pow)
|
212 | hex = hex.substring(2, hex.length)
|
213 | pow += 8
|
214 | return result
|
215 |
|
216 |
|
217 | ###*
|
218 | * Convert an integer into a little endian.
|
219 | * @param {Number} number The integer you want to convert.
|
220 | * @param {Boolean} [pad=true] Pad the little endian with zeroes.
|
221 | * @return {String} The little endian.
|
222 | ###
|
223 | stringifyLittleEndian: (number, pad=true) ->
|
224 | power = Math.floor((Math.log(number) / Math.LN2) / 8) * 8
|
225 | multiplier = Math.pow(2, power)
|
226 | value = Math.floor(number / multiplier)
|
227 | remainder = number % multiplier
|
228 | endian = ""
|
229 | if remainder > 255
|
230 | endian += @stringifyLittleEndian(remainder, false)
|
231 | else if power isnt 0
|
232 | endian += @dec2hex(remainder)
|
233 | endian += @dec2hex(value)
|
234 | if pad
|
235 | padding = 16 - endian.length
|
236 | endian += "0" for i in [0...padding] by 1
|
237 | return endian
|
238 |
|
239 |
|
240 | ###*
|
241 | * Turn a decimal into a hexadecimal.
|
242 | * @param {Number} dec The decimal.
|
243 | * @return {String} The hexadecimal.
|
244 | ###
|
245 | dec2hex: (dec) ->
|
246 | hex = dec.toString(16)
|
247 | if hex.length < 2 then hex = "0" + hex
|
248 | return hex
|
249 |
|
250 |
|
251 | ###*
|
252 | * Convert a binary string into a hex string.
|
253 | * @param {String} binary The binary encoded string.
|
254 | * @return {String} The hex encoded string.
|
255 | ###
|
256 | bin2hex: (binary) ->
|
257 | hex = ""
|
258 | for char in binary
|
259 | hex += char.charCodeAt(0).toString(16).replace(/^([\dA-F])$/i, "0$1")
|
260 | return hex
|
261 |
|
262 |
|
263 | ###*
|
264 | * Generate a uuid.
|
265 | * @param {Number} [length=32] The length of the UUID.
|
266 | * @return {String} The UUID.
|
267 | ###
|
268 | generateUuid: (length=32) ->
|
269 | length /= 2
|
270 | return @randomBytes(length).toString('hex').toUpperCase(0)
|
271 |
|
272 |
|
273 | module.exports = Crypto
|