Module: BetterAuth::Crypto
- Defined in:
- lib/better_auth/crypto.rb,
lib/better_auth/crypto/jwe.rb
Defined Under Namespace
Modules: JWE
Constant Summary collapse
- URL_SAFE_ALPHABET =
[*"a".."z", *"A".."Z", *"0".."9", "-", "_"].freeze
- MASK_64 =
(1 << 64) - 1
- KECCAK_ROUND_CONSTANTS =
[ 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 ].freeze
- KECCAK_ROTATION_OFFSETS =
[ [0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], [28, 55, 25, 21, 56], [27, 20, 39, 8, 14] ].freeze
Class Method Summary collapse
- .base64url_decode(value) ⇒ Object
- .base64url_encode(value) ⇒ Object
- .constant_time_compare(left, right) ⇒ Object
- .hmac_signature(value, secret, encoding: :base64) ⇒ Object
- .keccak256(value, encoding: :hex) ⇒ Object
- .keccak256_bytes(input) ⇒ Object
- .keccak_permute!(state) ⇒ Object
- .random_string(length = 32) ⇒ Object
- .rotate_left_64(value, shift) ⇒ Object
- .sha256(value, encoding: :hex) ⇒ Object
- .sign_jwt(payload, secret, expires_in: 3600) ⇒ Object
- .stringify_keys(value) ⇒ Object
- .symmetric_decode_jwt(token, secret, salt) ⇒ Object
- .symmetric_decrypt(key:, data:) ⇒ Object
- .symmetric_encode_jwt(payload, secret, salt, expires_in: 3600) ⇒ Object
- .symmetric_encrypt(key:, data:) ⇒ Object
- .to_checksum_address(address) ⇒ Object
- .uuid ⇒ Object
- .verify_hmac_signature(value, signature, secret, encoding: :base64) ⇒ Object
- .verify_jwt(token, secret) ⇒ Object
Class Method Details
.base64url_decode(value) ⇒ Object
129 130 131 |
# File 'lib/better_auth/crypto.rb', line 129 def base64url_decode(value) Base64.urlsafe_decode64(value.to_s) end |
.base64url_encode(value) ⇒ Object
125 126 127 |
# File 'lib/better_auth/crypto.rb', line 125 def base64url_encode(value) Base64.urlsafe_encode64(value.to_s, padding: false) end |
.constant_time_compare(left, right) ⇒ Object
69 70 71 72 73 |
# File 'lib/better_auth/crypto.rb', line 69 def constant_time_compare(left, right) return false unless left.bytesize == right.bytesize OpenSSL.fixed_length_secure_compare(left, right) end |
.hmac_signature(value, secret, encoding: :base64) ⇒ Object
59 60 61 62 |
# File 'lib/better_auth/crypto.rb', line 59 def hmac_signature(value, secret, encoding: :base64) digest = OpenSSL::HMAC.digest("SHA256", secret.to_s, value.to_s) (encoding == :base64url) ? base64url_encode(digest) : Base64.strict_encode64(digest) end |
.keccak256(value, encoding: :hex) ⇒ Object
45 46 47 48 |
# File 'lib/better_auth/crypto.rb', line 45 def keccak256(value, encoding: :hex) digest = keccak256_bytes(value.to_s.b) (encoding == :bytes) ? digest : digest.unpack1("H*") end |
.keccak256_bytes(input) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/better_auth/crypto.rb', line 140 def keccak256_bytes(input) rate = 136 state = Array.new(25, 0) padded = input.bytes padded << 0x01 padded << 0 while (padded.length % rate) != rate - 1 padded << 0x80 padded.each_slice(rate) do |block| block.each_with_index do |byte, index| state[index / 8] ^= byte << (8 * (index % 8)) end keccak_permute!(state) end state.pack("Q<*").byteslice(0, 32) end |
.keccak_permute!(state) ⇒ Object
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 |
# File 'lib/better_auth/crypto.rb', line 158 def keccak_permute!(state) KECCAK_ROUND_CONSTANTS.each do |round_constant| columns = Array.new(5) { |x| state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20] } deltas = Array.new(5) { |x| columns[(x - 1) % 5] ^ rotate_left_64(columns[(x + 1) % 5], 1) } 5.times do |x| 5.times { |y| state[x + (5 * y)] = (state[x + (5 * y)] ^ deltas[x]) & MASK_64 } end rotated = Array.new(25, 0) 5.times do |x| 5.times do |y| rotated[y + (5 * ((2 * x + 3 * y) % 5))] = rotate_left_64(state[x + (5 * y)], KECCAK_ROTATION_OFFSETS[x][y]) end end 5.times do |y| 5.times do |x| state[x + (5 * y)] = (rotated[x + (5 * y)] ^ ((~rotated[((x + 1) % 5) + (5 * y)]) & rotated[((x + 2) % 5) + (5 * y)])) & MASK_64 end end state[0] = (state[0] ^ round_constant) & MASK_64 end end |
.random_string(length = 32) ⇒ Object
32 33 34 |
# File 'lib/better_auth/crypto.rb', line 32 def random_string(length = 32) Array.new(length) { URL_SAFE_ALPHABET[SecureRandom.random_number(URL_SAFE_ALPHABET.length)] }.join end |
.rotate_left_64(value, shift) ⇒ Object
184 185 186 187 188 189 |
# File 'lib/better_auth/crypto.rb', line 184 def rotate_left_64(value, shift) shift %= 64 return value & MASK_64 if shift.zero? ((value << shift) | (value >> (64 - shift))) & MASK_64 end |
.sha256(value, encoding: :hex) ⇒ Object
40 41 42 43 |
# File 'lib/better_auth/crypto.rb', line 40 def sha256(value, encoding: :hex) digest = OpenSSL::Digest.digest("SHA256", value.to_s) (encoding == :base64url) ? base64url_encode(digest) : digest.unpack1("H*") end |
.sign_jwt(payload, secret, expires_in: 3600) ⇒ Object
102 103 104 105 106 107 108 |
# File 'lib/better_auth/crypto.rb', line 102 def sign_jwt(payload, secret, expires_in: 3600) claims = stringify_keys(payload).merge( "iat" => Time.now.to_i, "exp" => Time.now.to_i + expires_in.to_i ) JWT.encode(claims, secret.to_s, "HS256") end |
.stringify_keys(value) ⇒ Object
133 134 135 136 137 138 |
# File 'lib/better_auth/crypto.rb', line 133 def stringify_keys(value) return value.each_with_object({}) { |(key, object_value), result| result[key.to_s] = stringify_keys(object_value) } if value.is_a?(Hash) return value.map { |entry| stringify_keys(entry) } if value.is_a?(Array) value end |
.symmetric_decode_jwt(token, secret, salt) ⇒ Object
121 122 123 |
# File 'lib/better_auth/crypto.rb', line 121 def symmetric_decode_jwt(token, secret, salt) JWE.decode(token, secret, salt) end |
.symmetric_decrypt(key:, data:) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/better_auth/crypto.rb', line 90 def symmetric_decrypt(key:, data:) payload = JSON.parse(base64url_decode(data.to_s)) cipher = OpenSSL::Cipher.new("aes-256-gcm") cipher.decrypt cipher.key = OpenSSL::Digest.digest("SHA256", key.to_s) cipher.iv = base64url_decode(payload.fetch("iv")) cipher.auth_tag = base64url_decode(payload.fetch("tag")) cipher.update(base64url_decode(payload.fetch("data"))) + cipher.final rescue JSON::ParserError, KeyError, OpenSSL::Cipher::CipherError, ArgumentError nil end |
.symmetric_encode_jwt(payload, secret, salt, expires_in: 3600) ⇒ Object
117 118 119 |
# File 'lib/better_auth/crypto.rb', line 117 def symmetric_encode_jwt(payload, secret, salt, expires_in: 3600) JWE.encode(payload, secret, salt, expires_in: expires_in) end |
.symmetric_encrypt(key:, data:) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/better_auth/crypto.rb', line 75 def symmetric_encrypt(key:, data:) cipher = OpenSSL::Cipher.new("aes-256-gcm") cipher.encrypt cipher.key = OpenSSL::Digest.digest("SHA256", key.to_s) iv = SecureRandom.random_bytes(12) cipher.iv = iv ciphertext = cipher.update(data.to_s) + cipher.final payload = { "iv" => base64url_encode(iv), "data" => base64url_encode(ciphertext), "tag" => base64url_encode(cipher.auth_tag) } base64url_encode(JSON.generate(payload)) end |
.to_checksum_address(address) ⇒ Object
50 51 52 53 54 55 56 57 |
# File 'lib/better_auth/crypto.rb', line 50 def to_checksum_address(address) normalized = address.to_s.downcase.delete_prefix("0x") hash = keccak256(normalized) "0x" + normalized.chars.each_with_index.map do |char, index| (hash[index].to_i(16) >= 8) ? char.upcase : char end.join end |
.uuid ⇒ Object
36 37 38 |
# File 'lib/better_auth/crypto.rb', line 36 def uuid SecureRandom.uuid end |
.verify_hmac_signature(value, signature, secret, encoding: :base64) ⇒ Object
64 65 66 67 |
# File 'lib/better_auth/crypto.rb', line 64 def verify_hmac_signature(value, signature, secret, encoding: :base64) expected = hmac_signature(value, secret, encoding: encoding) constant_time_compare(expected, signature.to_s) end |
.verify_jwt(token, secret) ⇒ Object
110 111 112 113 114 115 |
# File 'lib/better_auth/crypto.rb', line 110 def verify_jwt(token, secret) decoded, = JWT.decode(token.to_s, secret.to_s, true, algorithm: "HS256") decoded rescue JWT::DecodeError nil end |