-
Notifications
You must be signed in to change notification settings - Fork 438
/
base32.rb
74 lines (67 loc) · 1.91 KB
/
base32.rb
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
module ROTP
class Base32
class Base32Error < RuntimeError; end
CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.each_char.to_a
SHIFT = 5
MASK = 31
class << self
def decode(str)
buffer = 0
idx = 0
bits_left = 0
str = str.tr('=', '').upcase
result = []
str.split('').each do |char|
buffer = buffer << SHIFT
buffer = buffer | (decode_quint(char) & MASK)
bits_left = bits_left + SHIFT
if bits_left >= 8
result[idx] = (buffer >> (bits_left - 8)) & 255
idx = idx + 1
bits_left = bits_left - 8
end
end
result.pack('c*')
end
def encode(b)
data = b.unpack('c*')
out = String.new
buffer = data[0]
idx = 1
bits_left = 8
while bits_left > 0 || idx < data.length
if bits_left < SHIFT
if idx < data.length
buffer = buffer << 8
buffer = buffer | (data[idx] & 255)
bits_left = bits_left + 8
idx = idx + 1
else
pad = SHIFT - bits_left
buffer = buffer << pad
bits_left = bits_left + pad
end
end
val = MASK & (buffer >> (bits_left - SHIFT))
bits_left = bits_left - SHIFT
out.concat(CHARS[val])
end
return out
end
# Defaults to 160 bit long secret (meaning a 32 character long base32 secret)
def random(byte_length = 20)
rand_bytes = SecureRandom.random_bytes(byte_length)
self.encode(rand_bytes)
end
# Prevent breaking changes
def random_base32(str_len = 32)
byte_length = str_len * 5/8
random(byte_length)
end
private
def decode_quint(q)
CHARS.index(q) || raise(Base32Error, "Invalid Base32 Character - '#{q}'")
end
end
end
end