1defmodule Comeonin.Bcrypt.Base64 do 2 @moduledoc """ 3 Module that provides base64 encoding for bcrypt. 4 5 Bcrypt uses an adapted base64 alphabet (using `.` instead of `+`, 6 starting with `./` and with no padding). 7 """ 8 9 use Bitwise 10 11 @decode_map {:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:ws,:ws,:bad,:bad,:ws,:bad,:bad, 12 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 13 :ws,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,0,1, 14 54,55,56,57,58,59,60,61,62,63,:bad,:bad,:bad,:eq,:bad,:bad, 15 :bad,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, 16 17,18,19,20,21,22,23,24,25,26,27,:bad,:bad,:bad,:bad,:bad, 17 :bad,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42, 18 43,44,45,46,47,48,49,50,51,52,53,:bad,:bad,:bad,:bad,:bad, 19 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 20 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 21 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 22 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 23 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 24 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 25 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad, 26 :bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad,:bad} 27 28 @doc """ 29 Encode using the adapted Bcrypt alphabet. 30 31 ## Examples 32 33 iex> Comeonin.Bcrypt.Base64.encode 'spamandeggs' 34 'a1/fZUDsXETlX1K' 35 """ 36 def encode(words), do: encode_l(words) 37 38 @doc """ 39 Decode using the adapted Bcrypt alphabet. 40 41 ## Examples 42 43 iex> Comeonin.Bcrypt.Base64.decode 'a1/fZUDsXETlX1K' 44 'spamandeggs' 45 """ 46 def decode(words), do: decode_l(words, []) 47 48 @doc """ 49 Shorten the salt to 128 bits + 4 zeros. 50 51 ## Examples 52 53 iex> Comeonin.Bcrypt.Base64.normalize '0CaBPBIAm2pgeQiCrTSa5O' 54 '0CaBPBIAm2pgeQiCrTSa5O' 55 iex> Comeonin.Bcrypt.Base64.normalize '0CaBPBIAm2pgeQiCrTSa5S' 56 '0CaBPBIAm2pgeQiCrTSa5O' 57 iex> Comeonin.Bcrypt.Base64.normalize '0CaBPBIAm2pgeQiCrTSa5B' 58 '0CaBPBIAm2pgeQiCrTSa5.' 59 """ 60 def normalize(salt) do 61 decode(salt) |> encode 62 end 63 64 defp b64e(val) do 65 elem({?., ?/, ?A, ?B, ?C, ?D, ?E, ?F, ?G, ?H, ?I, ?J, ?K, ?L, 66 ?M, ?N, ?O, ?P, ?Q, ?R, ?S, ?T, ?U, ?V, ?W, ?X, 67 ?Y, ?Z, ?a, ?b, ?c, ?d, ?e, ?f, ?g, ?h, ?i, ?j, ?k, ?l, 68 ?m, ?n, ?o, ?p, ?q, ?r, ?s, ?t, ?u, ?v, ?w, ?x, 69 ?y, ?z, ?0, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9}, val) 70 end 71 72 defp encode_l([]), do: [] 73 defp encode_l([a]) do 74 [b64e(a >>> 2), 75 b64e((a &&& 3) <<< 4)] 76 end 77 defp encode_l([a,b]) do 78 [b64e(a >>> 2), 79 b64e(((a &&& 3) <<< 4) ||| (b >>> 4)), 80 b64e((b &&& 15) <<< 2)] 81 end 82 defp encode_l([a,b,c|ls]) do 83 bb = (a <<< 16) ||| (b <<< 8) ||| c 84 [b64e(bb >>> 18), 85 b64e((bb >>> 12) &&& 63), 86 b64e((bb >>> 6) &&& 63), 87 b64e(bb &&& 63) | encode_l(ls)] 88 end 89 90 defp decode_l([], a), do: a 91 defp decode_l([c1,c2], a) do 92 bits2x6 = (b64d(c1) <<< 18) ||| (b64d(c2) <<< 12) 93 octet1 = bits2x6 >>> 16 94 a ++ [octet1] 95 end 96 defp decode_l([c1,c2,c3], a) do 97 bits3x6 = (b64d(c1) <<< 18) ||| (b64d(c2) <<< 12) ||| (b64d(c3) <<< 6) 98 octet1 = bits3x6 >>> 16 99 octet2 = (bits3x6 >>> 8) &&& 0xff 100 a ++ [octet1,octet2] 101 end 102 defp decode_l([c1,c2,c3,c4| cs], a) do 103 bits4x6 = (b64d(c1) <<< 18) ||| (b64d(c2) <<< 12) ||| (b64d(c3) <<< 6) ||| b64d(c4) 104 octet1 = bits4x6 >>> 16 105 octet2 = (bits4x6 >>> 8) &&& 0xff 106 octet3 = bits4x6 &&& 0xff 107 decode_l(cs, a ++ [octet1,octet2,octet3]) 108 end 109 110 defp b64d(val) do 111 b64d_ok(elem(@decode_map, val)) 112 end 113 114 defp b64d_ok(val) when is_integer(val), do: val 115 defp b64d_ok(val) do 116 raise ArgumentError, "Invalid character: #{val}" 117 end 118end 119