Skip to content

Commit 6ca2ffc

Browse files
committed
chore: wip
1 parent 4d5b9f6 commit 6ca2ffc

6 files changed

Lines changed: 277 additions & 258 deletions

File tree

packages/sha/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export * from './sha1'
22
export * from './sha256'
33
export * from './sha384'
4-
export * from './sha512'
4+
// export * from './sha512'

packages/sha/src/sha1.ts

Lines changed: 87 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/**
22
* Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
3+
* This implementation follows the FIPS 180-2 specification and is based on the
4+
* node-forge implementation.
35
*
46
* @author Chris Breuer
57
*/
@@ -41,8 +43,8 @@ export function create(): MessageDigest {
4143
// input buffer
4244
let _input = createBuffer()
4345

44-
// Fix array initialization with proper typing
45-
const _w: number[] = Array.from({ length: 80 }).fill(0)
46+
// used for word storage
47+
const _w: number[] = new Array(80).fill(0)
4648

4749
// message digest object
4850
const md: MessageDigest = {
@@ -84,31 +86,31 @@ export function create(): MessageDigest {
8486
},
8587

8688
/**
87-
* Updates the digest with the given message input. The given input can
88-
* treated as raw input (no encoding will be applied) or an encoding of
89-
* 'utf8' maybe given to encode the input using UTF-8.
89+
* Updates the digest with the given message input.
9090
*
9191
* @param msg the message input to update with.
9292
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
9393
*
9494
* @return this digest object.
9595
*/
9696
update(msg: string | ByteStringBuffer, encoding?: string) {
97+
if (!msg) {
98+
return md
99+
}
100+
97101
if (encoding === 'utf8') {
98102
msg = encodeUtf8(msg as string)
99103
}
100104

101105
// update message length
102106
const len = msg instanceof ByteStringBuffer ? msg.length() : msg.length
103107
md.messageLength += len
104-
const lenArr = [Math.floor(len / 0x100000000), len >>> 0]
108+
const lenArr = [(len / 0x100000000) >>> 0, len >>> 0]
105109
for (let i = md.fullMessageLength.length - 1; i >= 0; --i) {
106110
md.fullMessageLength[i] += lenArr[1]
107-
const carry = Math.floor(md.fullMessageLength[i] / 0x100000000)
111+
lenArr[1] = lenArr[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0)
108112
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0
109-
if (carry > 0 && i > 0) {
110-
md.fullMessageLength[i - 1] += carry
111-
}
113+
lenArr[0] = ((lenArr[1] / 0x100000000) >>> 0)
112114
}
113115

114116
// add bytes to input buffer
@@ -117,9 +119,9 @@ export function create(): MessageDigest {
117119
// process bytes
118120
_update(_state!, _w, _input)
119121

120-
// compact input buffer every 2K or if empty
121-
if (_input.read > 2048 || _input.length() === 0) {
122-
_input.compact()
122+
// compact input buffer every 2K bytes
123+
if (_input.length() > 2048) {
124+
_input = createBuffer(_input.bytes())
123125
}
124126

125127
return md
@@ -131,70 +133,65 @@ export function create(): MessageDigest {
131133
* @return a byte buffer containing the digest value.
132134
*/
133135
digest() {
134-
/* Note: Here we copy the remaining bytes in the input buffer and
135-
add the appropriate SHA-1 padding. Then we do the final update
136-
on a copy of the state so that if the user wants to get
137-
intermediate digests they can do so. */
138-
139-
/* Determine the number of bytes that must be added to the message
140-
to ensure its length is congruent to 448 mod 512. In other words,
141-
the data to be digested must be a multiple of 512 bits (or 128 bytes).
142-
This data includes the message, some padding, and the length of the
143-
message. Since the length of the message will be encoded as 8 bytes (64
144-
bits), that means that the last segment of the data must have 56 bytes
145-
(448 bits) of message and padding. Therefore, the length of the message
146-
plus the padding must be congruent to 448 mod 512 because
147-
512 - 128 = 448.
148-
149-
In order to fill up the message length it must be filled with
150-
padding that begins with 1 bit followed by all 0 bits. Padding
151-
must *always* be present, so if the message length is already
152-
congruent to 448 mod 512, then 512 padding bits must be added. */
153-
154136
const finalBlock = createBuffer()
155137
finalBlock.putBytes(_input.bytes())
156138

157139
// compute remaining size to be digested (include message length size)
158140
const remaining = (
159-
md.fullMessageLength[md.fullMessageLength.length - 1]
160-
+ md.messageLengthSize)
141+
md.fullMessageLength[md.fullMessageLength.length - 1] +
142+
md.messageLengthSize
143+
)
161144

162145
// add padding for overflow blockSize - overflow
163-
// _padding starts with 1 byte with first bit is set (byte value 128), then
164-
// there may be up to (blockSize - 1) other pad bytes
165146
const overflow = remaining & (md.blockLength - 1)
166147
if (_padding === null) {
167148
throw new Error('SHA-1 padding not initialized')
168149
}
169-
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow))
170150

171-
// serialize message length in bits in big-endian order; since length
172-
// is stored in bytes we multiply by 8 and add carry from next int
173-
let next: number, carry: number
151+
// add padding
152+
const padLength = overflow < 56 ? 56 - overflow : 120 - overflow
153+
finalBlock.putBytes(_padding.substr(0, padLength))
154+
155+
// serialize message length in bits in big-endian order
174156
let bits = md.fullMessageLength[0] * 8
157+
const finalState = {
158+
h0: _state!.h0,
159+
h1: _state!.h1,
160+
h2: _state!.h2,
161+
h3: _state!.h3,
162+
h4: _state!.h4,
163+
}
164+
175165
for (let i = 0; i < md.fullMessageLength.length - 1; ++i) {
176-
next = md.fullMessageLength[i + 1] * 8
177-
carry = (next / 0x100000000) >>> 0
166+
const next = md.fullMessageLength[i + 1] * 8
167+
const carry = (next / 0x100000000) >>> 0
178168
bits += carry
179169
finalBlock.putInt32(bits >>> 0)
180170
bits = next >>> 0
181171
}
182172
finalBlock.putInt32(bits)
183173

184-
const s2 = {
185-
h0: _state!.h0,
186-
h1: _state!.h1,
187-
h2: _state!.h2,
188-
h3: _state!.h3,
189-
h4: _state!.h4,
190-
}
191-
_update(s2, _w, finalBlock)
174+
// update state one last time
175+
_update(finalState, _w, finalBlock)
176+
177+
// build final hash value
192178
const rval = createBuffer()
193-
rval.putInt32(s2.h0)
194-
rval.putInt32(s2.h1)
195-
rval.putInt32(s2.h2)
196-
rval.putInt32(s2.h3)
197-
rval.putInt32(s2.h4)
179+
rval.putInt32(finalState.h0)
180+
rval.putInt32(finalState.h1)
181+
rval.putInt32(finalState.h2)
182+
rval.putInt32(finalState.h3)
183+
rval.putInt32(finalState.h4)
184+
185+
// reset state for next use
186+
_input = createBuffer()
187+
_state = {
188+
h0: 0x67452301,
189+
h1: 0xEFCDAB89,
190+
h2: 0x98BADCFE,
191+
h3: 0x10325476,
192+
h4: 0xC3D2E1F0,
193+
}
194+
198195
return rval
199196
},
200197
}
@@ -237,108 +234,56 @@ function _update(s: {
237234
}, w: number[], bytes: ByteStringBuffer) {
238235
// consume 512 bit (64 byte) chunks
239236
let t: number, a: number, b: number, c: number, d: number, e: number, f: number
240-
let i: number
241237
let len = bytes.length()
242238
while (len >= 64) {
243-
// the w array will be populated with sixteen 32-bit big-endian words
244-
// and then extended into 80 32-bit words according to SHA-1 algorithm
245-
// and for 32-79 using Max Locktyukhin's optimization
246-
247239
// initialize hash value for this chunk
248240
a = s.h0
249241
b = s.h1
250242
c = s.h2
251243
d = s.h3
252244
e = s.h4
253245

254-
// round 1
255-
for (i = 0; i < 16; ++i) {
256-
t = bytes.getInt32()
257-
w[i] = t
258-
f = d ^ (b & (c ^ d))
259-
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t
260-
e = d
261-
d = c
262-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
263-
c = ((b << 30) | (b >>> 2)) >>> 0
264-
b = a
265-
a = t
266-
}
267-
for (; i < 20; ++i) {
268-
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16])
269-
t = (t << 1) | (t >>> 31)
270-
w[i] = t
271-
f = d ^ (b & (c ^ d))
272-
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t
273-
e = d
274-
d = c
275-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
276-
c = ((b << 30) | (b >>> 2)) >>> 0
277-
b = a
278-
a = t
279-
}
280-
// round 2
281-
for (; i < 32; ++i) {
282-
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16])
283-
t = (t << 1) | (t >>> 31)
284-
w[i] = t
285-
f = b ^ c ^ d
286-
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t
287-
e = d
288-
d = c
289-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
290-
c = ((b << 30) | (b >>> 2)) >>> 0
291-
b = a
292-
a = t
246+
// The w array will be populated with sixteen 32-bit big-endian words
247+
for (let i = 0; i < 16; ++i) {
248+
w[i] = bytes.getInt32()
293249
}
294-
for (; i < 40; ++i) {
295-
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32])
296-
t = (t << 2) | (t >>> 30)
297-
w[i] = t
298-
f = b ^ c ^ d
299-
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t
300-
e = d
301-
d = c
302-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
303-
c = ((b << 30) | (b >>> 2)) >>> 0
304-
b = a
305-
a = t
306-
}
307-
// round 3
308-
for (; i < 60; ++i) {
309-
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32])
310-
t = (t << 2) | (t >>> 30)
311-
w[i] = t
312-
f = (b & c) | (d & (b ^ c))
313-
t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t
314-
e = d
315-
d = c
316-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
317-
c = ((b << 30) | (b >>> 2)) >>> 0
318-
b = a
319-
a = t
250+
251+
// Extend into 80 32-bit words
252+
for (let i = 16; i < 80; ++i) {
253+
t = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]
254+
w[i] = ((t << 1) | (t >>> 31)) >>> 0
320255
}
321-
// round 4
322-
for (; i < 80; ++i) {
323-
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32])
324-
t = (t << 2) | (t >>> 30)
325-
w[i] = t
326-
f = b ^ c ^ d
327-
t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t
256+
257+
// Round function
258+
for (let i = 0; i < 80; ++i) {
259+
if (i < 20) {
260+
f = (b & c) | ((~b) & d)
261+
t = 0x5A827999
262+
} else if (i < 40) {
263+
f = b ^ c ^ d
264+
t = 0x6ED9EBA1
265+
} else if (i < 60) {
266+
f = (b & c) | (b & d) | (c & d)
267+
t = 0x8F1BBCDC
268+
} else {
269+
f = b ^ c ^ d
270+
t = 0xCA62C1D6
271+
}
272+
273+
t = (((a << 5) | (a >>> 27)) + f + e + t + w[i]) >>> 0
328274
e = d
329275
d = c
330-
// `>>> 0` necessary to avoid iOS/Safari 10 optimization bug
331276
c = ((b << 30) | (b >>> 2)) >>> 0
332277
b = a
333278
a = t
334279
}
335280

336-
// update hash state
337-
s.h0 = (s.h0 + a) | 0
338-
s.h1 = (s.h1 + b) | 0
339-
s.h2 = (s.h2 + c) | 0
340-
s.h3 = (s.h3 + d) | 0
341-
s.h4 = (s.h4 + e) | 0
281+
// update state
282+
s.h0 = (s.h0 + a) >>> 0
283+
s.h1 = (s.h1 + b) >>> 0
284+
s.h2 = (s.h2 + c) >>> 0
285+
s.h3 = (s.h3 + d) >>> 0
286+
s.h4 = (s.h4 + e) >>> 0
342287

343288
len -= 64
344289
}
@@ -347,3 +292,5 @@ function _update(s: {
347292
export const sha1: { create: typeof create } = {
348293
create,
349294
}
295+
296+
export default sha1

0 commit comments

Comments
 (0)