hex-base64-translator.py (4160B) - raw


      1 #!/usr/bin/env python3
      2 # Copyright (C) 2020 Oscar Benedito <oscar@oscarbenedito.com>
      3 #
      4 # This program is free software: you can redistribute it and/or modify
      5 # it under the terms of the GNU Affero General Public License as
      6 # published by the Free Software Foundation, either version 3 of the
      7 # License, or (at your option) any later version.
      8 #
      9 # This program is distributed in the hope that it will be useful,
     10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 # GNU Affero General Public License for more details.
     13 #
     14 # You should have received a copy of the GNU Affero General Public License
     15 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
     16 
     17 # This script simply translates hexadecimal strings into Base64 strings (by
     18 # converting them into bits and then reading the bits as Base64) and the inverse
     19 # process.
     20 #
     21 # The goal of this program is make a password containing only characters in the
     22 # Base64 alphabet and then being able to split the secret between different
     23 # parties using ssss (<http://point-at-infinity.org/ssss/>) with the hexadecimal
     24 # option. With this, an attacker can't get any advantage by discarding unvalid
     25 # answers, since they are all valid (when running the program normally, you can
     26 # get "binary" secrets or "ASCII" secrets).
     27 #
     28 # All this trouble is due to the fact that I am not sure if there is a way for
     29 # an attacker with some shares of the secret to avoid making a brute-force
     30 # attack by knowing the implementation of ssss and anticipate binary (and
     31 # therefore invalid) results.
     32 
     33 import sys
     34 from getpass import getpass
     35 
     36 
     37 def char_to_bits(c):
     38     n = ord(c)
     39     if n >= 65 and n <= 90:
     40         return bin(n - 65)[2:].zfill(6)
     41     elif n >= 97 and n <= 122:
     42         return bin(n - 71)[2:].zfill(6)
     43     elif n >= 48 and n <= 57:
     44         return bin(n + 4)[2:].zfill(6)
     45     elif (c == '+'):
     46         return bin(62)[2:].zfill(6)
     47     elif (c == '/'):
     48         return bin(63)[2:].zfill(6)
     49     else:
     50         sys.exit('Error, ' + c + ' is not a Base64 character.', file=sys.stderr)
     51 
     52 
     53 def bits_to_char(s):
     54     n = int(s, 2)
     55     if n < 26:
     56         return chr(n + 65)
     57     elif n < 52:
     58         return chr(n + 71)
     59     elif n < 62:
     60         return chr(n - 4)
     61     elif n == 62:
     62         return '+'
     63     elif n == 63:
     64         return '/'
     65     else:
     66         sys.exit('Error, ' + s + ' (' + str(n) + ') is not a binary number lower than 64.', file=sys.stderr)
     67 
     68 
     69 def base64_to_hex(s):
     70     if len(s) % 2:
     71         print('WARNING: Number of Base64 characters is not multiple of 2. Adding zeros to string.', file=sys.stderr)
     72         s = 'A' + s
     73 
     74     ret = ''
     75     carry = ''
     76     while len(s) > 0:
     77         cs = s[:2]
     78         bs = char_to_bits(s[0]) + char_to_bits(s[1])
     79         ret += hex(int(bs, 2))[2:].zfill(3)
     80         s = s[2:]
     81 
     82     return ret
     83 
     84 
     85 def hex_to_base64(s):
     86     if len(s) % 3:
     87         print('WARNING: Number of hexadecimal values is not a multiple of 3. Adding zeros to string.', file=sys.stderr)
     88         s = '0'*(3 - (len(s) % 3)) + s
     89 
     90     ret = ''
     91     while len(s) > 0:
     92         bs = bin(int(s[:3], 16))[2:].zfill(12)
     93         ret += bits_to_char(bs[:6])
     94         ret += bits_to_char(bs[6:])
     95         s = s[3:]
     96 
     97     return ret
     98 
     99 
    100 if __name__ == '__main__':
    101     if len(sys.argv) != 2 or (sys.argv[1] != 'base64-to-hex' and sys.argv[1] != 'hex-to-base64'):
    102         sys.exit('Usage: ' + sys.argv[0] + ' base64-to-hex | hex-to-base64.')
    103 
    104     if sys.argv[1] == 'base64-to-hex':
    105         inp = getpass(prompt = 'Base64 secret: ')
    106         print('')
    107         out = base64_to_hex(inp)
    108         print('-'*80)
    109         print('Secret in hexadecimal:', out)
    110         print('-'*80)
    111 
    112     elif sys.argv[1] == 'hex-to-base64':
    113         inp = getpass(prompt = 'Hexadecimal secret: ')
    114         print('')
    115         out = hex_to_base64(inp)
    116         print('-'*80)
    117         print('Secret in Base64:', out)
    118         print('-'*80)
    119         if inp[0] == '0' and len(inp) % 2 == 0:
    120             out = hex_to_base64(inp[1:])
    121             print('-'*80)
    122             print('Due to SSSS having an output with an even number of characters, your secret could be:', out)
    123             print('-'*80)