Base64 and non standard

1k views Asked by At

I try to create a python client for bacula, but I have some problem with the authentication.

The algorithm is :


import hmac
import base64
import re

...

challenge = re.search("auth cram-md5 ()", data)
#exemple ''
passwd = 'b489c90f3ee5b3ca86365e1bae27186e'
hm = hmac.new(passwd, challenge).digest()
rep = base64.b64encode(hm).strp().rstrip('=')
#result with python : 9zKE3VzYQ1oIDTpBuMMowQ
#result with bacula client : 9z+E3V/YQ1oIDTpBu8MowB'

There's a way more simple than port the bacula's implemenation of base 64?

int
bin_to_base64(char *buf, int buflen, char *bin, int binlen, int compatible)
{
   uint32_t reg, save, mask;
   int rem, i;
   int j = 0;

   reg = 0;
   rem = 0;
   buflen--;                       /* allow for storing EOS */
   for (i=0; i >= (rem - 6);
      if (j 
2

There are 2 answers

0
AMADANON Inc. On

I HAVE CRACKED THIS.

I ran into exactly the same problem you did, and have just spent about 4 hours identifying the problem, and reimplementing it.

The problem is the Bacula's base64 is BROKEN, AND WRONG!

There are two problems with it:

The first is that the incoming bytes are treated as signed, not unsigned. The effect of this is that, if a byte has the highest bit set (>127), then it is treated as a negative number; when it is combined with the "left over" bits from previous bytes are all set to (binary 1).

The second is that, after b64 has processed all the full 6-bit output blocks, there may be 0, 2 or 4 bits left over (depending on input block modulus 3). The standard Base64 way to handle this is to multiply the remaining bits, so they are the HIGHEST bits in the last 6-bit block, and process them - Bacula leaves them as the LOWEST bits.

Note that some versions of Bacula may accept both the "Bacula broken base64 encoding" and the standard ones, for incoming authentication; they seem to use the broken one for their authentication.

def bacula_broken_base64(binarystring):
    b64_chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    remaining_bit_count=0
    remaining_bits=0
    output=""
    for inputbyte in binarystring:
        inputbyte=ord(inputbyte)
        if inputbyte>127:
            # REPRODUCING A BUG! set all the "remaining bits" to 1.
            remaining_bits=(1 << remaining_bit_count) - 1
        remaining_bits=(remaining_bits<<8)+inputbyte
        remaining_bit_count+=8
        while remaining_bit_count>=6:
            # clean up:
            remaining_bit_count-=6
            new64=(remaining_bits>>remaining_bit_count) & 63 # 6 highest bits
            output+=b64_chars[new64]
            remaining_bits&=(1 << remaining_bit_count) - 1
    if remaining_bit_count>0:
        output+=b64_chars[remaining_bits]

    return output

I realize it's been 6 years since you asked, but perhaps someone else will find this useful.

0
ncoghlan On

To verify your CRAM-MD5 implementation, it is best to use some simple test vectors and check combinations of (challenge, password, username) inputs against the expected output.

Here's one example (from http://blog.susam.in/2009/02/auth-cram-md5.html):

import hmac
username = '[email protected]'
passwd = 'drowssap'
encoded_challenge = 'PDc0NTYuMTIzMzU5ODUzM0BzZGNsaW51eDIucmRzaW5kaWEuY29tPg=='
challenge = encoded_challenge.decode('base64')
digest = hmac.new(passwd, challenge).hexdigest()
response = username + ' ' + digest
encoded_response = response.encode('base64')
print encoded_response
# Zm9vQHN1c2FtLmluIDY2N2U5ZmE0NDcwZGZmM2RhOWQ2MjFmZTQwNjc2NzIy

That said, I've certainly found examples on the net where the response generated by the above code differs from the expected response stated on the relevant site, so I'm still not entirely clear as to what is happening in those cases.