Module: Megar::Crypto
- Included in:
- Session
- Defined in:
- lib/megar/crypto.rb
Overview
A straight-forward “quirks-mode” transcoding of core crypto methods required to talk to Mega. Some of this reeks a bit .. maybe more idiomatic ruby approaches are possible.
Generally we’re using signed 32-bit by default here … I don’t think it’s necessary, but it makes comparison with the javascript implementation easier.
Javascript reference implementations quoted here are taken from the Mega javascript source.
Instance Method Summary collapse
-
#a32_to_base64(a) ⇒ Object
Returns a base64-encoding given an array
a
of 32-bit integers. -
#a32_to_str(a) ⇒ Object
Returns a packed string given an array
a
of 32-bit signed integers. -
#aes_cbc_decrypt(data, key) ⇒ Object
Returns AES-128 decrypted given
key
anddata
(String). -
#aes_cbc_decrypt_a32(data, key) ⇒ Object
Returns AES-128 decrypted given
key
anddata
(arrays of 32-bit signed integers). -
#aes_encrypt_a32(data, key) ⇒ Object
Returns AES-128 encrypted given
key
anddata
(arrays of 32-bit signed integers). -
#base64_mpi_to_a32(s) ⇒ Object
Returns multiple precision integer (MPI) as an array of 32-bit signed integers decoded from base64 string
s
. -
#base64_mpi_to_bn(s) ⇒ Object
Returns multiple precision integer (MPI) as a big integers decoded from base64 string
s
. -
#base64_to_a32(s) ⇒ Object
Returns an array
a
of 32-bit integers given a base64-encodedb
(String). -
#base64urldecode(data) ⇒ Object
Returns a string given
data
(base64-encoded String). -
#base64urlencode(data) ⇒ Object
Returns a base64-encoding given
data
(String). - #decompose_file_key(key) ⇒ Object
-
#decompose_rsa_private_key(key) ⇒ Object
Returns the 4-part RSA key as array of big integers [d, p, q, u] given
key
(String). -
#decompose_rsa_private_key_a32(key) ⇒ Object
Returns the 4-part RSA key as 32-bit signed integers [d, p, q, u] given
key
(String). -
#decrypt_base64_to_a32(data, key) ⇒ Object
Returns decrypted array of 32-bit integers representation of base64
data
decrypted usingkey
. -
#decrypt_base64_to_str(data, key) ⇒ Object
Returns decrypted string representation of base64
data
decrypted usingkey
. - #decrypt_file_attributes(f, key) ⇒ Object
- #decrypt_file_key(f) ⇒ Object
-
#decrypt_key(a, key) ⇒ Object
Returns a decrypted given an array
a
of 32-bit integers andkey
. -
#decrypt_session_id(csid, rsa_private_key) ⇒ Object
Returns the decrypted session id given base64 MPI
csid
and RSArsa_private_key
as array of big integers [d, p, q, u]. -
#hexstr_to_bstr(h) ⇒ Object
Returns a binary string given a string
h
of hex digits. -
#mpi_to_a32(s) ⇒ Object
Returns multiple precision integer (MPI) as an array of 32-bit unsigned integers decoded from raw string
s
This first 16-bits of the MPI is the MPI length in bits. -
#openssl_rsa_cipher(pqdu) ⇒ Object
Returns an OpenSSL RSA cipher object initialised with
pqdu
(array of integer cipher components) p: The first factor of n, the RSA modulus q: The second factor of n d: The private exponent. -
#openssl_rsa_decrypt(m, pqdu) ⇒ Object
Returns the private key decryption of
m
givenpqdu
(array of integer cipher components) This implementation uses OpenSSL RSA public key feature. -
#prepare_key(a) ⇒ Object
Returns encrypted key given an array
a
of 32-bit integers. -
#prepare_key_pw(password) ⇒ Object
Returns encrypted key given the plain-text
password
string. -
#rsa_decrypt(m, pqdu) ⇒ Object
Returns the private key decryption of
m
givenpqdu
(array of integer cipher components). -
#str_to_a32(b) ⇒ Object
Returns an array of 32-bit signed integers representing the string
b
. -
#stringhash(s, aeskey) ⇒ Object
Returns a base64-encoding of string
s
hashed withaeskey
key.
Instance Method Details
#a32_to_base64(a) ⇒ Object
Returns a base64-encoding given an array a
of 32-bit integers
Javascript reference implementation: function a32_to_base64(a)
126 127 128 |
# File 'lib/megar/crypto.rb', line 126 def a32_to_base64(a) base64urlencode(a32_to_str(a)) end |
#a32_to_str(a) ⇒ Object
Returns a packed string given an array a
of 32-bit signed integers
Javascript reference implementation: function a32_to_str(a)
104 105 106 107 108 |
# File 'lib/megar/crypto.rb', line 104 def a32_to_str(a) b = '' (a.size * 4).times { |i| b << ((a[i>>2] >> (24-(i & 3)*8)) & 255).chr } b end |
#aes_cbc_decrypt(data, key) ⇒ Object
Returns AES-128 decrypted given key
and data
(String)
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/megar/crypto.rb', line 79 def aes_cbc_decrypt(data, key) aes = OpenSSL::Cipher::Cipher.new('AES-128-CBC') aes.decrypt aes.padding = 0 aes.key = key aes.iv = "\0" * 16 d = aes.update(data) d = aes.final if d.empty? d end |
#aes_cbc_decrypt_a32(data, key) ⇒ Object
Returns AES-128 decrypted given key
and data
(arrays of 32-bit signed integers)
74 75 76 |
# File 'lib/megar/crypto.rb', line 74 def aes_cbc_decrypt_a32(data, key) str_to_a32(aes_cbc_decrypt(a32_to_str(data), a32_to_str(key))) end |
#aes_encrypt_a32(data, key) ⇒ Object
Returns AES-128 encrypted given key
and data
(arrays of 32-bit signed integers)
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/megar/crypto.rb', line 62 def aes_encrypt_a32(data, key) aes = OpenSSL::Cipher::Cipher.new('AES-128-ECB') aes.encrypt aes.padding = 0 aes.key = key.pack('l>*') aes.update(data.pack('l>*')).unpack('l>*') # e = aes.update(data.pack('l>*')).unpack('l>*') # e << aes.final # e.unpack('l>*') end |
#base64_mpi_to_a32(s) ⇒ Object
Returns multiple precision integer (MPI) as an array of 32-bit signed integers decoded from base64 string s
215 216 217 |
# File 'lib/megar/crypto.rb', line 215 def base64_mpi_to_a32(s) mpi_to_a32(base64urldecode(s)) end |
#base64_mpi_to_bn(s) ⇒ Object
Returns multiple precision integer (MPI) as a big integers decoded from base64 string s
221 222 223 224 225 |
# File 'lib/megar/crypto.rb', line 221 def base64_mpi_to_bn(s) data = base64urldecode(s) len = ((data[0].ord * 256 + data[1].ord + 7) / 8) + 2 data[2,len+2].unpack('H*').first.to_i(16) end |
#base64_to_a32(s) ⇒ Object
Returns an array a
of 32-bit integers given a base64-encoded b
(String)
Javascript reference implementation: function base64_to_a32(s)
134 135 136 |
# File 'lib/megar/crypto.rb', line 134 def base64_to_a32(s) str_to_a32(base64urldecode(s)) end |
#base64urldecode(data) ⇒ Object
Returns a string given data
(base64-encoded String)
Javascript reference implementation: function base64urldecode(data)
150 151 152 |
# File 'lib/megar/crypto.rb', line 150 def base64urldecode(data) Base64.urlsafe_decode64(data + '=' * ((4 - data.length % 4) % 4)) end |
#base64urlencode(data) ⇒ Object
Returns a base64-encoding given data
(String).
Javascript reference implementation: function base64urlencode(data)
142 143 144 |
# File 'lib/megar/crypto.rb', line 142 def base64urlencode(data) Base64.urlsafe_encode64(data).gsub(/=*$/,'') end |
#decompose_file_key(key) ⇒ Object
389 390 391 392 393 394 395 396 |
# File 'lib/megar/crypto.rb', line 389 def decompose_file_key(key) [ key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7] ] end |
#decompose_rsa_private_key(key) ⇒ Object
Returns the 4-part RSA key as array of big integers [d, p, q, u] given key
(String)
result = p: The first factor of n, the RSA modulus result = q: The second factor of n result = d: The private exponent. result = u: The CRT coefficient, equals to (1/p) mod q.
Javascript reference implementation: function api_getsid2(res,ctx)
262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/megar/crypto.rb', line 262 def decompose_rsa_private_key(key) privk = key.dup decomposed_key = [] offset = 0 4.times do |i| len = ((privk[0].ord * 256 + privk[1].ord + 7) / 8) + 2 privk_part = privk[0,len] # puts "\nl: ", len # puts "decrypted rsa part hex: \n", privk_part.unpack('H*').first decomposed_key << privk_part[2,privk_part.length].unpack('H*').first.to_i(16) privk.slice!(0,len) end decomposed_key end |
#decompose_rsa_private_key_a32(key) ⇒ Object
Returns the 4-part RSA key as 32-bit signed integers [d, p, q, u] given key
(String)
result = p: The first factor of n, the RSA modulus result = q: The second factor of n result = d: The private exponent. result = u: The CRT coefficient, equals to (1/p) mod q.
Javascript reference implementation: function api_getsid2(res,ctx)
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/megar/crypto.rb', line 237 def decompose_rsa_private_key_a32(key) privk = key.dup decomposed_key = [] # puts "decomp: privk.len:#{privk.length}" 4.times do len = ((privk[0].ord * 256 + privk[1].ord + 7) / 8) + 2 privk_part = privk[0,len] # puts "\nprivk_part #{base64urlencode(privk_part)}" privk_part_a32 = mpi_to_a32(privk_part) decomposed_key << privk_part_a32 # puts "decomp: len:#{len} privk_part_a32:#{privk_part_a32.length} first:#{privk_part_a32.first} last:#{privk_part_a32.last}" privk.slice!(0,len) end decomposed_key end |
#decrypt_base64_to_a32(data, key) ⇒ Object
Returns decrypted array of 32-bit integers representation of base64 data
decrypted using key
51 52 53 |
# File 'lib/megar/crypto.rb', line 51 def decrypt_base64_to_a32(data,key) decrypt_key(base64_to_a32(data), key) end |
#decrypt_base64_to_str(data, key) ⇒ Object
Returns decrypted string representation of base64 data
decrypted using key
56 57 58 |
# File 'lib/megar/crypto.rb', line 56 def decrypt_base64_to_str(data,key) a32_to_str(decrypt_base64_to_a32(data, key)) end |
#decrypt_file_attributes(f, key) ⇒ Object
383 384 385 386 387 |
# File 'lib/megar/crypto.rb', line 383 def decrypt_file_attributes(f,key) k = f['t'] == 0 ? decompose_file_key(key) : key rstr = aes_cbc_decrypt(base64urldecode(f['a']), a32_to_str(k)) JSON.parse( rstr.gsub("\x0",'').gsub(/^.*{/,'{')) end |
#decrypt_file_key(f) ⇒ Object
378 379 380 381 |
# File 'lib/megar/crypto.rb', line 378 def decrypt_file_key(f) key = f['k'].split(':')[1] decrypt_key(base64_to_a32(key), self.master_key) end |
#decrypt_key(a, key) ⇒ Object
Returns a decrypted given an array a
of 32-bit integers and key
Javascript reference implementation: function decrypt_key(cipher,a)
42 43 44 45 46 47 48 |
# File 'lib/megar/crypto.rb', line 42 def decrypt_key(a, key) b=[] (0..(a.length-1)).step(4) do |i| b.concat aes_cbc_decrypt_a32(a[i,4], key) end b end |
#decrypt_session_id(csid, rsa_private_key) ⇒ Object
Returns the decrypted session id given base64 MPI csid
and RSA rsa_private_key
as array of big integers [d, p, q, u]
Javascript reference implementation: function api_getsid2(res,ctx)
281 282 283 284 285 286 287 288 |
# File 'lib/megar/crypto.rb', line 281 def decrypt_session_id(csid,rsa_private_key) csid_bn = base64_mpi_to_bn(csid) sid_bn = rsa_decrypt(csid_bn,rsa_private_key) sid_hs = sid_bn.to_s(16) sid_hs = '0' + sid_hs if sid_hs.length % 2 > 0 sid = hexstr_to_bstr(sid_hs)[0,43] base64urlencode(sid) end |
#hexstr_to_bstr(h) ⇒ Object
Returns a binary string given a string h
of hex digits
372 373 374 375 376 |
# File 'lib/megar/crypto.rb', line 372 def hexstr_to_bstr(h) bstr = '' (0..h.length-1).step(2) {|n| bstr << h[n,2].to_i(16).chr } bstr end |
#mpi_to_a32(s) ⇒ Object
Returns multiple precision integer (MPI) as an array of 32-bit unsigned integers decoded from raw string s
This first 16-bits of the MPI is the MPI length in bits
Javascript reference implementation: function mpi2b(s)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/megar/crypto.rb', line 159 def mpi_to_a32(s) bs=28 bx2=1<<bs bm=bx2-1 bn=1 r=[0] rn=0 sb=256 c = nil sn=s.length return 0 if(sn < 2) len=(sn-2)*8 bits=s[0].ord*256+s[1].ord return 0 if(bits > len || bits < len-8) len.times do |n| if ((sb<<=1) > 255) sb=1 sn -= 1 c=s[sn].ord end if(bn > bm) bn=1 rn += 1 r << 0 end if(c & sb != 0) r[rn]|=bn end bn<<=1 end r end |
#openssl_rsa_cipher(pqdu) ⇒ Object
Returns an OpenSSL RSA cipher object initialised with pqdu
(array of integer cipher components) p: The first factor of n, the RSA modulus q: The second factor of n d: The private exponent. u: The CRT coefficient, equals to (1/p) mod q.
NB: this hacks the RSA object creation n a way that should work, but can’t get this to work exactly right with Mega yet
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/megar/crypto.rb', line 354 def openssl_rsa_cipher(pqdu) rsa = OpenSSL::PKey::RSA.new p, q, d, u = pqdu rsa.p, rsa.q, rsa.d = p, q, d rsa.n = rsa.p * rsa.q # # dmp1 = d mod (p-1) # rsa.dmp1 = rsa.d % (rsa.p - 1) # # dmq1 = d mod (q-1) # rsa.dmq1 = rsa.d % (rsa.q - 1) # # iqmp = q^-1 mod p? # rsa.iqmp = (rsa.q ** -1) % rsa.p # # ipmq = (rsa.p ** -1) % rsa.q # ipmq = rsa.p ** -1 % rsa.q rsa.e = 0 # 65537 rsa end |
#openssl_rsa_decrypt(m, pqdu) ⇒ Object
Returns the private key decryption of m
given pqdu
(array of integer cipher components) This implementation uses OpenSSL RSA public key feature.
NB: can’t get this to work exactly right with Mega yet
334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/megar/crypto.rb', line 334 def openssl_rsa_decrypt(m, pqdu) rsa = openssl_rsa_cipher(pqdu) chunk_size = 256 # hmm. need to figure out how to calc for "data greater than mod len" # number.size(self.n) - 1 : Return the maximum number of bits that can be handled by this key. decrypt_texts = [] (0..m.length - 1).step(chunk_size) do |i| pt_part = m[i,chunk_size] decrypt_texts << rsa.private_decrypt(pt_part,3) end decrypt_texts.join end |
#prepare_key(a) ⇒ Object
Returns encrypted key given an array a
of 32-bit integers
Javascript reference implementation: function prepare_key(a)
18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/megar/crypto.rb', line 18 def prepare_key(a) pkey = [0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56] 0x10000.times do (0..(a.length-1)).step(4) do |j| key = [0,0,0,0] 4.times {|i| key[i] = a[i+j] if (i+j < a.length) } pkey = aes_encrypt_a32(pkey,key) end end pkey end |
#prepare_key_pw(password) ⇒ Object
Returns encrypted key given the plain-text password
string
Javascript reference implementation: function prepare_key_pw(password)
34 35 36 |
# File 'lib/megar/crypto.rb', line 34 def prepare_key_pw(password) prepare_key(str_to_a32(password)) end |
#rsa_decrypt(m, pqdu) ⇒ Object
Returns the private key decryption of m
given pqdu
(array of integer cipher components). Computes m**d (mod n).
This implementation uses a Pure Ruby implementation of RSA private_decrypt
p: The first factor of n, the RSA modulus q: The second factor of n d: The private exponent. u: The CRT coefficient, equals to (1/p) mod q.
n = pq n is used as the modulus for both the public and private keys. Its length, usually expressed in bits, is the key length.
φ(n) = (p – 1)(q – 1), where φ is Euler’s totient function.
Choose an integer e such that 1 < e < φ(n) and gcd(e, φ(n)) = 1; i.e., e and φ(n) are coprime. e is released as the public key exponent
Determine d as d ≡ e−1 (mod φ(n)), i.e., d is the multiplicative inverse of e (modulo φ(n)). d is kept as the private key exponent.
More info: en.wikipedia.org/wiki/RSA_(algorithm)#Operation
Javascript reference implementation: function RSAdecrypt(m, d, p, q, u)
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/megar/crypto.rb', line 315 def rsa_decrypt(m, pqdu) p, q, d, u = pqdu if p && q && u m1 = Math.powm(m, d % (p-1), p) m2 = Math.powm(m, d % (q-1), q) h = m2 - m1 h = h + q if h < 0 h = h*u % q h*p+m1 else Math.powm(m, d, p*q) end end |
#str_to_a32(b) ⇒ Object
Returns an array of 32-bit signed integers representing the string b
Javascript reference implementation: function str_to_a32(b)
94 95 96 97 98 |
# File 'lib/megar/crypto.rb', line 94 def str_to_a32(b) a = Array.new((b.length+3) >> 2,0) b.length.times { |i| a[i>>2] |= (b.getbyte(i) << (24-(i & 3)*8)) } a.pack('l>*').unpack('l>*') # hack to force to signed 32-bit ... I don't think we really need to do this, but it makes comparison with end |
#stringhash(s, aeskey) ⇒ Object
Returns a base64-encoding of string s
hashed with aeskey
key
Javascript reference implementation: function stringhash(s,aes)
114 115 116 117 118 119 120 |
# File 'lib/megar/crypto.rb', line 114 def stringhash(s,aeskey) s32 = str_to_a32(s) h32 = [0,0,0,0] s32.length.times {|i| h32[i&3] ^= s32[i] } 16384.times {|i| h32 = aes_encrypt_a32(h32, aeskey) } a32_to_base64([h32[0],h32[2]]) end |