Skip to content

Commit b06c400

Browse files
committed
hash padding
1 parent 968a31f commit b06c400

File tree

3 files changed

+82
-27
lines changed

3 files changed

+82
-27
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ All notable changes to this project will be documented in this file. The format
189189
### Security
190190
---
191191

192+
### [1.9.18] - 2025-12-02
193+
194+
### Added
195+
196+
- Added checks for messages that are too long for the hash function.
197+
198+
### Changed
199+
200+
- Changed the Hash class to use BigInt for padding.
201+
- Changed the way padding is calculated for big endian and little endian.
202+
203+
---
204+
192205
### [1.9.17] - 2025-12-01
193206

194207
### Added

src/primitives/Hash.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -168,48 +168,42 @@ abstract class BaseHash {
168168
* @returns Returns an array denoting the padding.
169169
*/
170170
private _pad (): number[] {
171-
//
172-
let len = this.pendingTotal
171+
const len = this.pendingTotal
173172
const bytes = this._delta8
174173
const k = bytes - ((len + this.padLength) % bytes)
175174
const res = new Array(k + this.padLength)
176175
res[0] = 0x80
177-
let i
176+
let i: number
178177
for (i = 1; i < k; i++) {
179178
res[i] = 0
180179
}
181180

182181
// Append length
183-
len <<= 3
184-
let t
182+
const maxBits = 1n << BigInt(this.padLength * 8)
183+
let totalBits = BigInt(len) * 8n
184+
185+
if (totalBits >= maxBits) {
186+
throw new Error('Message too long for this hash function')
187+
}
188+
189+
const lengthBytes = this.padLength
185190
if (this.endian === 'big') {
186-
for (t = 8; t < this.padLength; t++) {
187-
res[i++] = 0
191+
const lenArray = new Array<number>(lengthBytes)
192+
193+
for (let b = lengthBytes - 1; b >= 0; b--) {
194+
lenArray[b] = Number(totalBits & 0xffn)
195+
totalBits >>= 8n
188196
}
189197

190-
res[i++] = 0
191-
res[i++] = 0
192-
res[i++] = 0
193-
res[i++] = 0
194-
res[i++] = (len >>> 24) & 0xff
195-
res[i++] = (len >>> 16) & 0xff
196-
res[i++] = (len >>> 8) & 0xff
197-
res[i++] = len & 0xff
198+
for (let b = 0; b < lengthBytes; b++) {
199+
res[i++] = lenArray[b]
200+
}
198201
} else {
199-
res[i++] = len & 0xff
200-
res[i++] = (len >>> 8) & 0xff
201-
res[i++] = (len >>> 16) & 0xff
202-
res[i++] = (len >>> 24) & 0xff
203-
res[i++] = 0
204-
res[i++] = 0
205-
res[i++] = 0
206-
res[i++] = 0
207-
208-
for (t = 8; t < this.padLength; t++) {
209-
res[i++] = 0
202+
for (let b = 0; b < lengthBytes; b++) {
203+
res[i++] = Number(totalBits & 0xffn)
204+
totalBits >>= 8n
210205
}
211206
}
212-
213207
return res
214208
}
215209
}

src/primitives/__tests/Hash.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,54 @@ describe('Hash', function () {
101101
)
102102
})
103103

104+
describe('BaseHash padding and endianness', () => {
105+
it('encodes length in big-endian for SHA1', () => {
106+
const sha1 = new (hash as any).SHA1()
107+
;(sha1 as any).pendingTotal = 12345
108+
const pad = (sha1 as any)._pad() as number[]
109+
const padLength = (sha1 as any).padLength as number
110+
const lengthBytes = pad.slice(-padLength)
111+
112+
const totalBits = BigInt(12345) * 8n
113+
const expected = new Array<number>(padLength)
114+
let tmp = totalBits
115+
for (let i = padLength - 1; i >= 0; i--) {
116+
expected[i] = Number(tmp & 0xffn)
117+
tmp >>= 8n
118+
}
119+
120+
expect(lengthBytes).toEqual(expected)
121+
})
122+
123+
it('encodes length in little-endian for RIPEMD160', () => {
124+
const ripemd = new (hash as any).RIPEMD160()
125+
;(ripemd as any).pendingTotal = 12345
126+
const pad = (ripemd as any)._pad() as number[]
127+
const padLength = (ripemd as any).padLength as number
128+
const lengthBytes = pad.slice(-padLength)
129+
130+
const totalBits = BigInt(12345) * 8n
131+
const expected = new Array<number>(padLength)
132+
let tmp = totalBits
133+
for (let i = 0; i < padLength; i++) {
134+
expected[i] = Number(tmp & 0xffn)
135+
tmp >>= 8n
136+
}
137+
138+
expect(lengthBytes).toEqual(expected)
139+
})
140+
141+
it('throws when message length exceeds maximum encodable bits', () => {
142+
const sha1 = new (hash as any).SHA1()
143+
;(sha1 as any).padLength = 1
144+
;(sha1 as any).pendingTotal = 40
145+
146+
expect(() => {
147+
;(sha1 as any)._pad()
148+
}).toThrow(new Error('Message too long for this hash function'))
149+
})
150+
})
151+
104152
describe('PBKDF2 vectors', () => {
105153
for (let i = 0; i < PBKDF2Vectors.length; i++) {
106154
const v = PBKDF2Vectors[i]

0 commit comments

Comments
 (0)