M_Crypto (formerly Blowfish and Bcrypt project)

I tend to have squirrel moments too.

Doug ???

or “Dug” :slight_smile:

I just released v.1.1 of the project here:

https://www.github.com/ktekinay/Blowfish

This ensures compatibility with 64-bit, introduces Bcrypt.Verify, and a “stress test” that compares the results against PHP to ensure compatibility.

Meow !

1 Like

After posting, I found a bug (naturally). The current release is 1.1.1. Same link.

On WP, https://en.wikipedia.org/wiki/PBKDF2, under Alternatives to PBKDF2, it is stated:

I got a similar answer when asking for password encryption schemes on SO: http://crypto.stackexchange.com/a/39670/38944

So, bcrypt is apparently better than PBKDF2. Even better would be to use scrypt or Argon2.

But what has not been explained here is that a Xojo implementation inherently brings along an extra weakness: As the Xojo impl is slower than the original C impl, it means one is forced to use fewer iterations to perform the decryption in an acceptable time. OTOH, a brute force attack can then use the faster C implementation and instantly gains an advantage. I wonder if that’s significant at all, though. Perhaps not. It’s not that Xojo’s code is a million times slower, but perhaps up to 10 times, which is not much of a magnitude to matter much.

Thoughts?

Actually… I just only now had a look at Kem’s project. As I understand bcrypt, it should have an “interations” (or “cost”) parameter, just like PBKDF2 does, see https://en.wikipedia.org/wiki/Bcrypt. But I can’t find one in Kem’s code. So, this is incomplete, it seems to me.

Awww, shoot. It’s called “rounds”. But it means the “cost” parameter. Why rounds? Was that the name used in the original C source? Rounds makes little sense as it implies it’d mean iterations, when, in fact, it means an exponent (iter = 2^cost).

What surprises me even more, though, is that the “rounds” parameter is used only to be encoded into the $…$ salt string. It’s not actually used to control the number of iterations the key is derived.

Kem, have you ever checked if your code can inter-operarate with OTHER bcrypt implementations (e.g. the openssl cmd can do it, it seems)? Or have you only tested it against itself?

I believe “rounds” was taken from the C code, but don’t recall now.

Because we use bcrypt now for hashing, wrote a test that compares the result to that from PHP. It’s been run thousands of times with no deviation, but I don’t recall if I’ve changed that parameter during the test. I’ll look in a bit.

Hi Kem, it may be that the PHP code used only a single iteration – then your results would match. Or I somehow overlooked something in the code and it’s all correct.

Here’s what you’d have to be able to observe: The higher the “rounds” value, the more time the function takes, exponentially. If time remains constant, then your code is just plain wrong, because that’s the whole purpose of the rounds (aka cost) parameter: To for the algo to take more time, so that a brute force has to also take more time for each attempt.

I did vary the cost, and the test itself is in the project. The test code:

  //
  // Stress test Bcrypt to make sure the result
  // matches the output from PHP
  //
  
  dim alphabet() as string = split( _
  "abcdefghijklmnopqrstuvwxyz " + _
  "!@#$%^&*()_+" + _
  "=-/.,?><[]{}" + _
  "¡™£¢?§¶•ªº–??€‹›??‡°·‚—±" + _
  """'" + _
  "œ?á鮆¥üîø?¬", _
  "" )
  
  dim passwords() as string
  
  const kMinLetters = 5
  const kMaxLetters = 15
  const kRounds = 50
  
  dim r as new Random
  
  for letterCount as integer = kMinLetters to kMaxLetters
    for round as integer = 1 to kRounds
      dim wordArr() as string
      
      for letterIndex as integer = 1 to letterCount
        dim randomCharIndex as integer = r.InRange( 0, alphabet.Ubound )
        dim char as string = alphabet( randomCharIndex )
        wordArr.Append char
      next letterIndex
      
      
      dim word as string = join( wordArr, "" )
      passwords.Append word
    next round
  next letterCount
  
  const kMinCost = 7
  const kMaxCost = 12
  
  for cost as integer = kMinCost to kMaxCost
    AddToResult "Cost: " + str( cost )
    
    dim salt as string = Bcrypt_MTC.GenerateSalt( cost )
    
    for each pw as string in passwords
      dim myHash as string = Bcrypt_MTC.Bcrypt( pw, salt )
      if not PHPVerify( pw, myHash ) then
        AddToResult "PHP no match: " + pw
      elseif not Bcrypt_MTC.Verify( pw, myHash ) then
        AddToResult "Internal no match: " + pw
      end if
      if UserCancelled then
        AddToResult "Cancelled"
        exit for cost
      end if
    next
    fldResult.Refresh
  next
  
  AddToResult "Bcrypt Stress Test Finished"
  
  return

BTW, this runs significantly faster in 64-bit, for what that’s worth.

Okay - false alarm. The code appears to be sound.

Sorry for that, I should have tested it first. The results show exactly what I claimed should happen: Changing the “rounds” parameter will affect the time the calculation takes. So all seems to be good.

However, your test code do not seem to test the Bcrypt function that takes a “rounds” instead of a “salt” parameter at all. That’s not decent testing, you know. That’s part of what made me suspicious: That function doesn’t get called and it seems it never passes the rounds parameter further on, so I made the (incorrect) assumption that this isn’t right.

Also, anyone testing your test code as an example will be surprised that one Bcrypt call will take 5-10 seconds (in 32 bit builds). It should be made more clear that they should then use a smaller rounds value to decrease the time. Of course, it’ll also downgrade the security a little, but that’s still better than annoying your users with a delay of 10s every time they want to log in while your have Bcrypt check their password.

Whoa, where do you see 5-10 seconds? And have you compared that to another implementation with the same cost setting to check the time difference?

I built for 32 bit with 2016r1.1. Though I ran it in the debugger (shouldn’t matter, though). On a 2.8 GHz Mac Pro.
Ran for 7.1s for rounds=10. And twice the time for rounds=11, as expected.

Update:

Huh, okay. Running in the debugger DOES affect runtime significantly. Odd. When built for 32 bit, it runs in 0.8s (in debugger now 5.3s). And for 64 bit 0.32s. That’s more like it.

Thomas, I did all I could think of to optimize this code, including things I would never recommend to anyone else. Perhaps you can spot other areas of improvement.

Sorry, I’ve already spent too much time on this topic, and most of it was even misguided due to my own fault. I have no decided to use the more modern “scrypt”, which is open source, so I’ve just built a dylib that I link to from Xojo. Works fine. The only challenge left will be to port it to Windows later on.

I just updated this project and changed it over to M_Crypto from Blowfish_MTC. It’s available here:

https://github.com/ktekinay/M_Crypto

It now includes a native AES encrypter and a handy all-purpose encryption/decryption app in the project. The README has more information.

I just released v.2.1. These are the release notes:

  • Refactored to streamline code.
  • Changed NullsWithCount padding to conform to standard. It will now add padding in all cases, even if the block is already the right size. This means the last padding byte can be 0x01, something that wasn’t allowed in the previous version. Depadding will still work on something encrypted like that unless you decrypt in blocks and the last block is <= BlockSize.
  • Added the CLI project and reorganized files in general.

I’m particularly happy with the CLI project. You can now encrypt/decrypt on the command line with a variety of options. From the help:

  mtccrypto - Encrypt/Decrypt/Bcrypt utilty v.1.0

Help:
  -h, --help           Show help
  -x STR, --execute=STR
                       The action to perform [can be: `Encrypt', `E',
                       `Decrypt', `D', `Bcrypt', `B', `Verify-bcrypt',
                       `V']
  --salt=STR           The specific salt to use for Bcrypt in the
                       correct format (testing option not normally used)
  --rounds=INTEGER     The number of rounds to use for Bcrypt if salt
                       is not specified [default 10]
  --against-hash=STR   The hash to verify with Bcrypt
  -e STR, --encrypter=STR
                       Encrypter to use for encrypt/decrypt [can be:
                       `aes-128', `aes-128-cbc', `aes-128-ecb', `aes
                       -192', `aes-192-cbc', `aes-192-ecb', `aes-256',
                       `aes-256-cbc', `aes-256-ecb', `bf', `bf-cbc',
                       `bf-ecb']
  -k STR, --key=STR    The encryption key as plain, hex, or Base64
  --key-file=FILE      The file that contains the key
  --key-stdin          The key has been piped in through StdIn
  -K STR, --key-encoding=STR
                       The encoding of the key [default None] [can be:
                       `None', `N', `Hex', `H', `Base64', `B']
  -H STR, --key-hash=STR
                       The hash to apply to the key [default None] [can
                       be: `None', `N', `MD5', `M', `SHA1', `S1',
                       `SHA256', `S256', `SHA512', `S512']
  -p STR, --padding=STR
                       The padding to use [default PKCS] [can be:
                       `Nulls-Only', `Nulls-With-Count', `PKCS']
  --iv=STR             With CBC encryption, the initial vector as plain or hex
  -D STR, --data-encoding=STR
                       The encoding of the data [default None] [can be:
                       `None', `N', `Hex', `H', `Base64', `B']
  --data-file=FILE     The file that contains the data
  --data-stdin         The data has been piped in through StdIn
  -O STR, --output-encoding=STR
                       Encode the result of encryption/decryption
                       [default None] [can be: `None', `N', `Hex', `H',
                       `Base64', `B']
  --output-file=FILE   The output file that will be overwritten
  --eol                Include an EOL in the output [default TRUE
                       unless --output-file is used]

Notes:
  If data is not given and no file specified, it will be pulled from
  StdIn.

  When encrypting/decrypting, if a key is not given it will be
  requested unless the --data-stdin switch is present.

Is it possible to use this implementation with iOS Xojo projects?

Thanks.

p.s. It would be great thing, even for Xojo R&D, to add supported platforms tags in text and/or technical documentation.

Forum for Xojo Programming Language and IDE. Copyright © 2021 Xojo, Inc.