Skip to content

Instantly share code, notes, and snippets.

@tsdorsey
Last active December 3, 2015 22:33
Show Gist options
  • Save tsdorsey/d56fdb25d83246f18942 to your computer and use it in GitHub Desktop.
Save tsdorsey/d56fdb25d83246f18942 to your computer and use it in GitHub Desktop.

Revisions

  1. tsdorsey revised this gist Dec 3, 2015. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions hmac-sha-1.rb
    Original file line number Diff line number Diff line change
    @@ -75,9 +75,6 @@ def _validateHashedPassword(hashedPassword)
    # Test cases to validate the verifyHashedPassword method.
    def testKnown()
    for password, hash in [
    # From servicing.
    ['Test@User123', 'AHb24e6gSeTkwxQXBs5fUrUs82vNrIp9adkPwaT3IAWAnt3Il80JG20hU1z22PUzhg=='],
    ['Test@User123', 'ABvKjwCdZ5+ducW/piXi78Q6e0WsNTPNdbEcfZ52s22yW2XXH8ochASKpuHlc8ktyQ=='],
    # Top 25 most used passwords in 2014.
    ['123456', 'AH9EGs/zg3gzLLdKuEe44+ShE07rl7K1uoQ1fXFM1GjdWdz8MamHpQhaeFgaUDzv0A=='],
    ['password', 'ACHYpKal/KMrWR+b2ucEFmYhCc5K/RfXJPUq0KAXxUjvN76EuxSnH7Cr0wNsnmPTmA=='],
  2. tsdorsey renamed this gist Nov 25, 2015. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. tsdorsey created this gist Nov 25, 2015.
    116 changes: 116 additions & 0 deletions hmac-sha-1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,116 @@
    require 'base64'
    require 'openssl'
    require 'securerandom'

    # Creates and interprets .NET RFC2898 version 0 encoded passwords.
    # Password format is Base64.strict_encode64(\0x0 + salt + subkey)

    def encode(str)
    Base64.strict_encode64(str)
    end

    def decode(str)
    Base64.strict_decode64(str)
    end

    ITERATIONS = 1000
    SUBKEYLENGTH = 256 / 8 # 256 bit aka 32 bytes.
    SALTLENGTH = 128 / 8 # 128 bit aka 16 bytes.
    VERSION = decode('AA==') # 0b00000000.


    # From the .NET helper WebMatrix.
    def hashPassword(password)
    raise ArgumentError.new('password') if password.nil?
    saltBytes = SecureRandom.random_bytes(SALTLENGTH)
    return _encodePasswordV0(saltBytes, rfc2898DeriveBytes(password, saltBytes, ITERATIONS, SUBKEYLENGTH))
    end

    def verifyHashedPassword(hashedPassword, password)
    _validateHashedPassword(hashedPassword)
    raise ArgumentError.new('password') if password.nil?

    saltBytes = extractSaltBytes(hashedPassword)
    subkeyBytes = extractSubkeyBytes(hashedPassword)

    generatedSubkeyBytes = rfc2898DeriveBytes(password, saltBytes, ITERATIONS, SUBKEYLENGTH)
    return generatedSubkeyBytes == subkeyBytes
    end


    # From .NET cryto.
    def extractSaltBytes(hashedPassword)
    _validateHashedPassword(hashedPassword)
    hashedPasswordBytes = decode(hashedPassword)

    # Pull the salt out. It's the second byte and runs until salt length.
    return hashedPasswordBytes[1..SALTLENGTH]
    end

    def extractSubkeyBytes(hashedPassword)
    _validateHashedPassword(hashedPassword)
    hashedPasswordBytes = decode(hashedPassword)

    # Pull out the subkey. It's at 1+SALTLENGTH until the end (we've already
    # checked total length).
    subkeyBytes = hashedPasswordBytes[(1+SALTLENGTH)..-1]
    end

    def rfc2898DeriveBytes(password, salt, iterations, subkeyLength)
    return OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, iterations, subkeyLength)
    end

    def _encodePasswordV0(salt, subkey)
    return encode(VERSION + salt + subkey)
    end

    def _validateHashedPassword(hashedPassword)
    raise ArgumentError.new('hashedPassword') if hashedPassword.nil?
    hashedPasswordBytes = decode(hashedPassword)
    raise ArgumentError.new('hash length') unless hashedPasswordBytes.length == (1 + SALTLENGTH + SUBKEYLENGTH)
    raise ArgumentError.new('hash version') unless hashedPasswordBytes[0] == VERSION
    end


    # Test cases to validate the verifyHashedPassword method.
    def testKnown()
    for password, hash in [
    # From servicing.
    ['Test@User123', 'AHb24e6gSeTkwxQXBs5fUrUs82vNrIp9adkPwaT3IAWAnt3Il80JG20hU1z22PUzhg=='],
    ['Test@User123', 'ABvKjwCdZ5+ducW/piXi78Q6e0WsNTPNdbEcfZ52s22yW2XXH8ochASKpuHlc8ktyQ=='],
    # Top 25 most used passwords in 2014.
    ['123456', 'AH9EGs/zg3gzLLdKuEe44+ShE07rl7K1uoQ1fXFM1GjdWdz8MamHpQhaeFgaUDzv0A=='],
    ['password', 'ACHYpKal/KMrWR+b2ucEFmYhCc5K/RfXJPUq0KAXxUjvN76EuxSnH7Cr0wNsnmPTmA=='],
    ['12345', 'ADIYyvBkHa8lySq31mg0pAsT9sLe429VDDecKSP0dsVs2iR0ZJn5y2jAIaK0d6n5kg=='],
    ['12345678', 'AO1d73Vwc9T7ELETWKQmZXM9Hvuq1X54dPsIzG67JghRUPGYiWfKiAchJwhu6p8TJA=='],
    ['qwerty', 'AKOG2irQDjpBop3XaPQBqhCH86PoIGf9HJixEfp8WvepsYCALj1JnPhSICoi4smBiw=='],
    ['123456789', 'AIFzYbLfY8ucUJf3F4BltNx6YLAHysEM7gO5vuAd+QkV9OO1KjJwRgxCAIxS21oK0A=='],
    ['1234', 'AHjq0nf1wqG05TRTMTJiBWNYFECfqkndMpvKxyXbsbVXgADdkmN/jLB8yT7+hSV5Dw=='],
    ['baseball', 'AGvOuBwVMzUTjL0xupvqnv4eRbTioc3uIQcc5z8elsBWnb5crvWrXcZDBzPh5HC5Tw=='],
    ['dragon', 'AL7Ur4DS9FJLqNHaT5nWIrX5HBRC7IWIG6fYvnvESlcTAIjC1rCxzlZUpOmqOuAo6g=='],
    ['football', 'AJlA1pBtUFGzEHCqYA8Ns9Fh2zdZzzSwhNCK23S50FDK20YNDnbiDzQIW4xNlB5Jug=='],
    ['1234567', 'AM1alPNB4IPW2lIVst6bK28KDhWo+2Ezm7R/yC7+dhDhhq2RZ3JZ5oZrLE5974ASWw=='],
    ['monkey', 'AKn4uYPd63v47cBLll96E8PS1aEm90P3pwlLG+oQs1KFkeDYszdn6K2g0xIiDOOOuw=='],
    ['letmein', 'AL0Dy8TxuFGa/E4sda2YXv6hJIH+KCwE0WlwcNmiuMDuj7Fec0eEGDgRbFYGklUCYA=='],
    ['abc123', 'AGYl/6ZefhOGZTQuJ6itnjSOOokRpvfx1lCCXnRTXE/zHYDKwW2mFfhjdRvjnLI5Zg=='],
    ['111111', 'AGf75M6AboBq6yxzIbk7+26IZzQ3coDKUTVNBqLC3j6KKD2F1IzK8sPAaYSNdTtXDg=='],
    ['mustang', 'AAcX+qlW53M+USTu+Q5AN899NJTf4AJM/GQ8lQ5jaWVWZjceTJk/F6LUAuxHqWN7pQ=='],
    ['access', 'AKNT+A6rEp/D0/WFOgZAijZ1ifAs8k+GrWQGNQFUYAyUrt+HzA3ibo3UiVb445CU3g=='],
    ['shadow', 'AN/cBXaQ86Lksk2EDmr5Iu+MfOZjxwHuhKUPRFM67IuK/EpEuTBfMoBX9MwRVBH71w=='],
    ['master', 'AJuGySYECMScUY3lU4m2+BdeMNk/Qg1FUlZCyz5U8wYxtZrBgO2Vm0bMoPQGOc1mRA=='],
    ['michael', 'AEhCExzV1169K6PTGOYYm2z7X0kBs/+D+CqAp4prsgYePHJVfKcugmkMchb0/e9mKg=='],
    ['superman', 'ABdvz5mu9pqVPibF+Y/xHkyIasMblxk+t6XQQ4vRGiXTAZ4Aue+BINc5U36I2etMoQ=='],
    ['696969', 'ACq4T2oUestvtjOrMnUkoOSMRAd/d8ZD5BgsDgrldtJGvF/rSJkWrZJtwg08Xs5Q+w=='],
    ['123123', 'AKJfsCwXdHqfs5O+3FJwnkTP+tKafirFlRcOYWqVDvWQ2oWkUphUiXz5i3nSLhDF5g=='],
    ['batman', 'AKXTvBN0teL3OksQYtxp7//5bRlT97cgjivBmTYkYb80CUMPtADffdik6REO2935qQ=='],
    ['trustno1', 'AAzh2KHhb/qln6ZcxPweacXhITuaAWmOYV9+8/xAdGW3XMVWBWtp9iE2Rs4gQPy7mw==']
    ]
    if verifyHashedPassword(hash, password)
    puts "#{hash} === #{password}"
    else
    puts "#{hash} =/= #{password}"
    end
    end

    return nil
    end