2

I am hashing passwords in MariaDB using:

ENCRYPT('password', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))

Which produces something like:

$6$3b502db443d64283$BNSYWsf3T0e4xT23GJW/QPpKvzLidio5zk9v7kCE.wk4dtNo4avMzBxvqoWc0Y5ihj/zVwtGCwZRfTFur0BnI1

The format here is $6 meaning SHA512, then the $salt$ and then the computed password hash. So the computed hash alone is

BNSYWsf3T0e4xT23GJW/QPpKvzLidio5zk9v7kCE.wk4dtNo4avMzBxvqoWc0Y5ihj/zVwtGCwZRfTFur0BnI1

As I understand it this uses crypt(3) in the background. If I write a short bit of C such as:

#define _GNU_SOURCE
#include <crypt.h>
#include <stdio.h>

int main() {
  char * blah = crypt("password", "$6$3b502db443d64283");
  puts(blah);
}

Taking care to put in the same random salt as the RAND() function generated for MySQL and prepending the $6 to use SHA512, then I do indeed get the same result:

$6$3b502db443d64283$BNSYWsf3T0e4xT23GJW/QPpKvzLidio5zk9v7kCE.wk4dtNo4avMzBxvqoWc0Y5ihj/zVwtGCwZRfTFur0BnI1

Again the computed hash itself is:

BNSYWsf3T0e4xT23GJW/QPpKvzLidio5zk9v7kCE.wk4dtNo4avMzBxvqoWc0Y5ihj/zVwtGCwZRfTFur0BnI1

Now if I use CryptoJS and wanted to compare that Hash in JS code rather than in the DB (or in - heaven forfend! - C), then I find that I have a problem. For example here is my CryptoJS code:

var CryptoJS = require('crypto-js');
var SHA512 = require('crypto-js/sha512');
  console.log(SHA512("3b502db443d64283password")).toString();

I prepend the salt to the password as this function takes only a single argument. The result is:

105865ba5bf0649410927131204a1228260e59e95b1b53942001b98835dca4aedfe7039a00060fdba8531c44c83cd3b7bec4864865915938d2ed7ed477254f61

Ok. So I recognise that that is a Hex encoding of the Hashed password. So I'll change the encoding...but to what? I'll try Base64:

  `console.log(SHA512("3b502db443d64283password")).toString(CryptoJS.enc.Base64);`

The output is:

EFhlulvwZJQQknExIEoSKCYOWelbG1OUIAG5iDXcpK7f5wOaAAYP26hTHETIPNO3vsSGSGWRWTjS7X7UdyVPYQ==

I'm starting to see a problem here. The encoding used by crypt(3) isn't base64. What is it? I look in the man page:

The characters in "salt" and "encrypted" are drawn from the set [a-zA-Z0-9./].

So I'm left with these questions:

  1. What is the encoding used by crypt(3)? Is it a standard or just a chosen character set.

  2. Is there any way to compare hashes generated with two different character sets like this? I mean is there an underlying representation they share of the hashed password? Can I access it?

  3. How do I know how many rounds each function is making? Is there any way to know without examining the source code?

  4. Is my assumption that I prepend the salt correct when using CryptoJS?

Daniel C
  • 1,332
  • 9
  • 15
  • 1
    I won't post it as an answer because I'm not sure, but I suspect crypt(3) uses the [B64 Encoding](https://en.wikipedia.org/wiki/Base64#Radix-64_applications_not_compatible_with_Base64) (see first linked bullet-point). So, in order from 0-63: `[./A-Za-z]`, without padding. Converting to and from "the" Base64 is then a simple `tr` (or equivalent), and adding (or removing) padding. – Tim Čas Aug 09 '17 at 12:43
  • 2
    The encoding *isn't* SHA-512 either. It is SHA-based iterated one. SHA alone cannot be used for any kind of secure password hashing at al, salted or not, so [key stretching by iteration is used](https://en.wikipedia.org/wiki/Key_stretching). – Antti Haapala -- Слава Україні Aug 09 '17 at 12:54
  • @TimČas that looks like a very good lead. Seems very likely it is B64 encoding (in no way confusing!). I haven't fiigured out the necessary transposition but if I take it you right it ought to also be possible to generate the Hex value from the B64. – Daniel C Aug 09 '17 at 13:40
  • @TimČas Source code says you're right: https://github.com/lattera/glibc/blob/master/crypt/sha512-crypt.c – Daniel C Aug 09 '17 at 16:05
  • 1
    @DanielC: I would say that (if anything) this is a flaw in Base64, not in crypt(3). I can find documentation [dating 1979 (Version 7 Unix)](http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/man/man3/crypt.3) referencing the B64 encoding --- meaning, B64 most likely predates Base64. Here is a dated source: [PDF version of the manual](http://web.cuzuco.com/~cuzuco/v7/v7vol1.pdf). – Tim Čas Aug 10 '17 at 12:17
  • @TimČas yes absolutely. I've just got so used to Base64 that it took me by surprise. Sadly I haven't got time at the moment to bridge that gap, but I don't actually have to. I just found it interesting. – Daniel C Aug 10 '17 at 16:29
  • @DanielC: In Javascript, you can try the approach [provided here](https://stackoverflow.com/a/10726800/485088). A faster approach might be to have a lookup array instead of an object, that is: `var mapping = [..., '.', '/', 'A', ...];` so that `mapping['A'.charCodeAt(0)] == '.'`, `mapping['B'.charCodeAt(0)] == '/'`, etc. But as usual: when in doubt, profile. – Tim Čas Aug 11 '17 at 10:28
  • @TimČas but since Base64 has padding won't there have to be a bit of jiggery pokery to handle that? – Daniel C Aug 11 '17 at 11:46
  • @DanielC: Yes, but it's trivial. The amount of padding required depends only on the *length* of the Base64 string (not its contents), and can be computed with this formula: `(4 - base64_str.length % 4) % 4` (n.b.: while this can *technically* result in 3 characters of padding, it never will, as Base64 never has the length required to do so). For completeness, the formula based on the *source* length: `(3 - source_str.length % 3) % 3`. – Tim Čas Aug 11 '17 at 22:01
  • @DanielC: In other words: `base64_str += '='.repeat((4 - base64_str.length % 4) % 4);`. Note, however, that padding is optional (or even forbidden) in some contexts. Also, feel free to use `x & 3` instead of `x % 4`. – Tim Čas Aug 11 '17 at 22:03

0 Answers0