Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace AES-CBC with AES-GCM and HMAC-SHA-1 with HMAC-SHA-256. #420

Merged

Conversation

potatosalad
Copy link
Contributor

First, this pull request may not be ready to merge quite yet. I wanted to get some feedback as to whether the direction I took is an acceptable one and if so, whether there needs to be any modifications prior to merging.

I'd also like to apologize for the length of my writing here, I wanted to make sure I had done my homework before submitting these recommendations. I have tried to summarize everything towards the beginning for those experiencing reading fatigue.

Summary of changes

Note: Whenever I say "deprecate" below, I really mean "soft deprecate" or "begin the transition away from" rather than an immediate hard deprecation.

  1. Deprecate AES256-CBC with HMAC SHA-1 in favor of AES128-GCM with AES256-GCM Key Wrapping in Plug.Crypto.MessageEncryptor.

  2. Deprecate HMAC SHA-1 in favor of HMAC SHA-256 in Plug.Crypto.MessageVerifier.

  3. Maintain backwards compatibility in Plug.Crypto.MessageEncryptor and Plug.Crypto.MessageVerifier so existing session cookies can be verified and decrypted, but new writes should use the new recommendations.

  4. Provide better methods of upgrading algorithms in the future while maintaining backwards compatibility (for example, 2-3 years from now, the recommended standards might be AES-GCM-SIV or the winning algorithm of the CAESAR competition for Authenticated Encryption and XMSS for Hash-Based Signatures).

    This was done by using a modified form of JWE Compact Serialization and JWS Compact Serialization which just includes the algorithm name as the "header" in a list of Base64url without padding encoded terms. For example BASE64URL("HS256") || '.' || BASE64URL("message") || '.' || BASE64URL(SIGNATURE) is the basic format of a signature token using HMAC with SHA-256.

    This should make it easy to provide backwards compatibility support for older algorithms by examining the protected header prior to performing verification or decryption.

  5. Deprecate encrypt_and_sign/3,4 in favor of encrypt/3. Deprecate verify_and_decrypt/3,4 in favor of decrypt/3.

    AES-GCM computes the authentication tag using the internal GHASH function. It's not really the same thing as a signature. Future encryption standards, like AES-GCM-SIV with its POLYVAL function, may compute additional tags/values which "authenticate" encrypted data before/after decryption, but behave differently from what we would normally consider a signature (verifying whether a particular message is from a specific sender).

Questions for discussion
  1. AES-GCM mode is only supported in OTP 18+ and OpenSSL 1.0.1+. If there is any concern for supporting OTP 17 or older versions of OpenSSL, would it be better to switch algorithms based on whether support for AES-GCM is detected?
  2. I chose AES128-GCM for the actual content encryption since the key is randomly generated and encrypted with AES256-GCM Key Wrapping. Would there be any preferences to use AES256-GCM for both operations?
  3. I chose HMAC SHA-256 for the default signing algorithm. Other supported modes are HMAC SHA-384 and HMAC SHA-512. Users may choose these other modes by specifying the digest type. For example, HMAC SHA-512 can be used with Plug.Crypto.MessageVerifier.sign(message, sign_secret, :sha512). Are there any preferences for something other than HMAC SHA-256 as the default?
  4. Any issues or improvements to the design for future algorithm changes and backwards compatibility?

Why are these changes necessary?

Short Explanation

AES256-CBC with HMAC SHA-1 uses an encryption key and signing key which are weakly linked. This allows a valid signing key and an invalid encryption key to erroneously decrypt a message resulting in garbage data returned. This occurs when a compatible initialization vector is randomly generated which returns a correctly padded plain text upon decryption with the wrong key, causing the tests to fail about 1 out of 120 runs.

Beyond that, AES-CBC in general is being phased out in many other communities/products/services and replaced with things like AES-CCM, AES-GCM, and CHACHA20-POLY1305. SHA-1 is also no longer recommended for future cryptographic use.

I would like to recommend the following two changes:

  1. For authenticated encryption: begin phasing out AES256-CBC with HMAC SHA-1 and replace it with AES128-GCM with AES256-GCM Key Wrapping.
  2. For symmetric signatures: begin phasing out HMAC SHA-1 and replace it with HMAC SHA-256.
Medium Explanation

While running the tests for Plug locally, I noticed the occasional failure for the tests related to Plug.Crypto.MessageEncryptor.

After closer examination, I found that the failure occurs approximately 0.8% of the time (or roughly 1 out of 120 runs) based on the frequency I measured on my machine.

I wound up writing a property test with ExCheck to demonstrate the issue, but have not included it in this pull request due to compilation issues with triq.

property :encrypt_and_decrypt do
  bytes_gen = fn (min) ->
    size_dom = bind(non_neg_integer(), fn (size) -> max(min, size) end)
    bind(size_dom, &:crypto.strong_rand_bytes/1)
  end
  secrets_dom = such_that({large, wrong} in {bytes_gen.(64), bytes_gen.(32)}
    when binary_part(large, 0, min(byte_size(large), byte_size(wrong))) !== binary_part(wrong, 0, min(byte_size(large), byte_size(wrong))))
  generator = bind({binary(), secrets_dom},
    fn ({data, {large, wrong}}) ->
      right = binary_part(large, 0, 32)
      encrypted = ME.encrypt(data, right, right)
      large_encrypted = ME.encrypt(data, large, large)
      {encrypted, large_encrypted, data, right, wrong, large}
    end)
  for_all {encrypted, large_encrypted, data, right, wrong, large} in generator do
    :error == ME.decrypt(encrypted, right, wrong) and
    :error == ME.decrypt(encrypted, wrong, right) and
    :error == ME.decrypt(encrypted, wrong, wrong) and
    {:ok, data} == ME.decrypt(encrypted, right, right) and
    :error == ME.decrypt(encrypted, right, large) and
    {:ok, data} == ME.decrypt(encrypted, large, right) and
    :error == ME.decrypt(encrypted, large, large) and
    :error == ME.decrypt(large_encrypted, large, wrong) and
    :error == ME.decrypt(large_encrypted, wrong, large) and
    :error == ME.decrypt(large_encrypted, wrong, wrong) and
    :error == ME.decrypt(large_encrypted, large, wrong) and
    {:ok, data} == ME.decrypt(large_encrypted, large, large) and
    :error == ME.decrypt(large_encrypted, large, right) and
    {:ok, data} == ME.decrypt(large_encrypted, right, large) and
    :error == ME.decrypt(large_encrypted, right, right)
  end
end

(ignore this paragraph if you know how quick check works) It essentially runs the for_all block ~100 times with randomly generated values as specified by the generator. If false is ever returned, it will attempt to shrink or reduce the generated values to the smallest form that still produces a failure and report on the shrunk values.

Here is an example walkthrough of the failure:

alias Plug.Crypto.MessageEncryptor, as: ME

# The encryption key and signing key.
secret      = "abcdefghabcdefghabcdefghabcdefgh"
sign_secret = "abcdefghabcdefghabcdefghabcdefgh"

# This will generate a random 128-bit initialization vector
# internally and embed it in the output. ~0.8% of the time,
# this random IV causes the issues mentioned below.
encrypted = ME.encrypt_and_sign(<<0, "hełłoworld", 0>>, secret, sign_secret, :aes_cbc256)

# Here are 5 example outputs with an IV that fails.
# encrypted = "ekZjb3Z6MVQybnBQaFhjd2ZkU3RhUT09LS0rVHVqVEdaVjc1aXc2NlBZVTdBRE1RPT0=##vqnoVqaVEEoRqWpBPoAYVGDOhBE="
# encrypted = "OCs5ZU4rcGRpNk9JYmFmaENqK1pXZz09LS05cGNUd0RNaUlRYm1BME52VFFycU5BPT0=##fvaT36-fXf35dzkSWAXP1s_9qSA="
# encrypted = "Tms4cHk5SUhPNGJicjNHL3JxeDlHZz09LS1MZkVicXJtLzFkWEY2NVprenRmZVdBPT0=##sHFyI_rNHS088_zkWj9ip5ITETc="
# encrypted = "anNqSm5xbXFhUjRuc1R4ZW9DbVUvQT09LS1oQmxlNlg3d0c3RkF1emtHVXRwN3F3PT0=##ZBkVGHSma6Ovm7s97lByFrWVVhU="
# encrypted = "WUFKbmhsRWdCck5YL1pMdDNQOTB4UT09LS13b3dpeW1OQ2VZTVdkenpCZjN0ZkhnPT0=##BjhlwiqJE8rrxtIx81nHAOPEOoI="

# The invalid key that should cause any form of decryption to fail.
badkey = "12345678123456781234567812345678"

# This returns an error before attempting to decrypt because
# the signing key is invalid.
:error = ME.verify_and_decrypt(encrypted, secret, badkey)

# This passes the signature verification step, but should also
# return an error because the encryption key is invalid.
:error = ME.verify_and_decrypt(encrypted, badkey, sign_secret)

# However, in ~0.8% of test cases, the following is returned.
{:ok, garbage} = ME.verify_and_decrypt(encrypted, badkey, sign_secret)

# Since the signing key is valid, the HMAC SHA-1 comparison
# verifies the signature and then decrypts the cipher text
# with the invalid encryption key which produces what :crypto
# thinks is valid plain text, but is actually garbage data.

The same example using AES128-GCM with AES256-GCM Key Wrapping does not have the same problems:

alias Plug.Crypto.MessageEncryptor, as: ME

# The encryption key and signing key.
secret      = "abcdefghabcdefghabcdefghabcdefgh"
sign_secret = "abcdefghabcdefghabcdefghabcdefgh"

# This will generate a random 128-bit initialization vector
# internally and embed it in the output. ~0.8% of the time,
# this random IV causes the issues mentioned below.
encrypted = ME.encrypt(<<0, "hełłoworld", 0>>, secret, sign_secret)

# Here are 5 example encryption outputs.
# encrypted = "QTEyOEdDTQ.PFBcghWDMaENoq0RPv6eoG4LkCT4lgTYGfMX-TUPB0S2InQYvM1RtvNQx1g.4rkEq56i7UtAhahx.D6298TfSD_TrVeieCEI.RnXW3GRKd_dvqyHXNzTfbg"
# encrypted = "QTEyOEdDTQ.peM8qJNbCMMkmcbLcGrms-MkEvVnqRLOF24kiaaRYkTlB9lLJNiXdiWc0Vk.1fq3yiyF_W1QYKwc.eWw0o78pnlml48I0RT8.s8QGudvjBWPgzPdzP13sPg"
# encrypted = "QTEyOEdDTQ.tz-AJON6vU9j1K5j5dC6la8diApGUPrfHMgYD7-8NYpE1X9_iUoLMlvVlLE.ZCiqqgsKLlVhyK6O.6aOaWYtC3uvdIJZSkNQ.yLyFA37-t5uOsb_AVhbVJw"
# encrypted = "QTEyOEdDTQ.NFQxH9YbtWWkPB_KczhqQKdwoE8gw7Ua0YfZCHHFamt8OnpuQXjx1YM8E7g.4UPXtKOceRq_yBHs.jut9L7lEtwKKZ4XwXIk.5zld79nBpwpK2oFTQLNxTA"
# encrypted = "QTEyOEdDTQ.fwBEx-1mcDcJ4T6LTW7H2PkG-iy97e_g9Udd_8BFbgiAbdmLOxcRWDvX7E0.di0DRHOxj-lKMfKD.xelHgwpNQRyaZ6wyr5I.J0oA9_C0kz3Gutxx2MZfFA"

# The invalid key that should cause any form of decryption to fail.
badkey = "12345678123456781234567812345678"

# This returns an error before attempting to decrypt because
# the key wrap additional authenticated data is invalid.
:error = ME.decrypt(encrypted, secret, badkey)

# This returns an error before attempting to decrypt because
# the key wrap encryption key is invalid.
:error = ME.decrypt(encrypted, badkey, sign_secret)
Long Explanation

The steps involved for AES256-CBC with HMAC SHA-1 encryption:

  1. Given the following parameters:
    • Plain Text (PT or message)
    • 256-bit AES Content Encryption Key (CEK or secret)
    • ?-byte HMAC SHA-1 Authentication Key (AK or sign_secret)
  2. Randomly generate a 128-bit AES-CBC Initialization Vector (IV).
  3. Pad PT with PKCS-7 padding such that size is a multiple of 16 (PaddedPT).
  4. AES256-CBC encrypt PaddedPT with CEK and IV to get Cipher Text (CT).
  5. Base64 encode CT and IV and join with "--" (CTwithIV).
  6. Base64url encode CTwithIV to get the Signing Input (SI).
  7. HMAC with SHA-1 the SI and Base64url encode the result (TAG).
  8. Join SI and TAG with "##" (ENC).

The steps involved for AES256-CBC with HMAC SHA-1 decryption:

  1. Given the following parameters:
    • Encrypted Token (ENC or encrypted)
    • 256-bit AES Content Encryption Key (CEK or secret)
    • ?-byte HMAC SHA-1 Authentication Key (AK or sign_secret)
  2. Split ENC with "##" to get SI and TAG.
  3. HMAC with SHA-1 the SI and Base64url encode the result (CHALLENGE).
  4. Securely compare CHALLENGE and TAG, if they don't match, return :error.
  5. Base64url decode SI to get CTwithIV.
  6. Split CTwithIV with "--" and Base64 decode to get CT and IV.
  7. AES256-CBC decrypt CT with CEK and IV to get PaddedPT.
  8. Unpad PaddedPT with PKCS-7 padding to get PT.

Here is a reduced example of AES256-CBC with HMAC SHA-1 minus the PKCS-7 padding part to better demonstrate the problem.

# Reduced Example of AES256-CBC with HMAC SHA-1 problem

## Variables
# Mapping of encrypt_and_sign parameters to variables
#   message     = plain_text
#   secret      = cek
#   sign_secret = ak
plain_text = "abcdefghijklmnop" # 16-byte Plain Text
cek        = << 0 :: 256 >>     # 256-bit AES Content Encryption Key
ak         = << 1 :: 256 >>     # 256-bit HMAC SHA-1 Authentication Key
iv         = << 2 :: 128 >>     # 128-bit AES-CBC Initialization Vector

## Encrypt
cipher_text = :crypto.block_encrypt(:aes_cbc256, cek, iv, plain_text)
cipher_tag  = :crypto.hmac(:sha, ak, iv <> cipher_text)

# cipher_text = <<194,240,87,8,253,143,118,144,93,51,164,208,139,231,194,186>>
# cipher_tag  = <<82,118,235,137,55,139,240,93,113,97,203,181,0,52,112,91,86,253,9,154>>

## Decrypt
challenge = :crypto.hmac(:sha, ak, iv <> cipher_text)
decrypted =
  if challenge === cipher_tag do
    :crypto.block_decrypt(:aes_cbc256, cek, iv, cipher_text)
  else
    :error
  end
decrypted !== :error     # => true
decrypted === plain_text # => true
decrypted                # => "abcdefghijklmnop"

## Invalid Variables
# Mapping of verify_and_decrypt parameters to variables
#   secret      = bad_kw_cek
#   sign_secret = bad_kw_aad
bad_cek = << 3 :: 256 >>
bad_ak  = << 4 :: 256 >>

## Decrypt with invalid secret and invalid sign_secret (should return :error)
challenge = :crypto.hmac(:sha, bad_ak, iv <> cipher_text)
decrypted =
  if challenge === cipher_tag do
    :crypto.block_decrypt(:aes_cbc256, bad_cek, iv, cipher_text)
  else
    :error
  end
decrypted !== :error     # => false

## Decrypt with valid secret and invalid sign_secret (should return :error)
challenge = :crypto.hmac(:sha, bad_ak, iv <> cipher_text)
decrypted =
  if challenge === cipher_tag do
    :crypto.block_decrypt(:aes_cbc256, cek, iv, cipher_text)
  else
    :error
  end
decrypted !== :error     # => false

## Decrypt with invalid secret and valid sign_secret (should return :error)
challenge = :crypto.hmac(:sha, ak, iv <> cipher_text)
decrypted =
  if challenge === cipher_tag do
    :crypto.block_decrypt(:aes_cbc256, bad_cek, iv, cipher_text)
  else
    :error
  end
decrypted !== :error     # => true
decrypted === plain_text # => false
decrypted                # => <<192,161,187,41,100,123,31,150,232,114,113,156,3,219,119,120>>

The steps involved for AES128-GCM with AES256-GCM Key Wrapping encryption:

  1. Given the following parameters:
    • Plain Text (PT or message)
    • 256-bit AES Content Encryption Key for Key Wrap (KWCEK or secret)
    • ?-byte AEAD Additional Authenticated Data for Key Wrap (KWAAD or sign_secret)
  2. Randomly generate a 128-bit AES-GCM Content Encryption Key (CEK).
  3. Randomly generate a 96-bit AES-GCM Initialization Vector (IV).
  4. AES128-GCM encrypt PT with CEK, IV, and "A128GCM" as AAD to get Cipher Text (CT) and Cipher Tag (TAG).
  5. Randomly generate a 96-bit AES-GCM Initialization Vector for Key Wrap (KWIV).
  6. AES256-GCM encrypt CEK with KWCEK, KWIV, and KWAAD to get the Key Wrap Cipher Text (KWCT) and Key Wrap Cipher Tag (KWTAG).
  7. Join KWCT, KWTAG, and KWIV to form the Encrypted Key (ENCKEY).
  8. Base64url encode without padding AAD, ENCKEY, IV, CT, and TAG and join with "." (ENC).

The steps involved for AES128-GCM with AES256-GCM Key Wrapping decryption:

  1. Given the following parameters:
    • Encrypted Token (ENC or encrypted)
    • 256-bit AES Content Encryption Key for Key Wrap (KWCEK or secret)
    • ?-byte AEAD Additional Authenticated Data for Key Wrap (KWAAD or sign_secret)
  2. Split ENV with "." and Base64url decode without padding to get AAD, ENCKEY, IV, CT, and TAG.
  3. Split ENCKEY by 256, 128, and 96 bits to get KWCT, KWTAG, and KWIV.
  4. AES256-GCM decrypt KWCT and KWTAG with KWCEK, KWIV, and KWAAD to get the AES-GCM Content Encryption Key (CEK), if decryption fails return :error.
  5. AES128-GCM decrypt CT and TAG with CEK, IV, and AAD to get PT.

Here is a reduced example of AES128-GCM with AES256-GCM Key Wrapping.

# Reduced Example of AES128-GCM with AES256-GCM Key Wrap

## Variables
# Mapping of encrypt_and_sign parameters to variables
#   message     = plain_text
#   secret      = kw_cek
#   sign_secret = kw_aad
plain_text = "abcdefghijklmnop" # 16-byte Plain Text
kw_cek     = << 0 :: 256 >>     # 256-bit AES Content Encryption Key for Key Wrap
kw_aad     = << 1 :: 256 >>     # AEAD Additional Authenticated Data for Key Wrap
kw_iv      = << 2 :: 96 >>      # 96-bit AES-GCM Initialization Vector for Key Wrap
cek        = << 3 :: 128 >>     # 128-bit randomly generated AES Content Encryption Key
aad        = <<>>               # AEAD Additional Authenticated Data
iv         = << 4 :: 96 >>      # 96-bit AES-GCM Initialization Vector

## Encrypt
{   cipher_text,    cipher_tag} = :crypto.block_encrypt(:aes_gcm, cek, iv, {aad, plain_text})
{kw_cipher_text, kw_cipher_tag} = :crypto.block_encrypt(:aes_gcm, kw_cek, kw_iv, {kw_aad, cek})

# cipher_text    = <<245,85,117,170,125,8,201,100,98,248,175,247,59,46,13,82>>
# cipher_tag     = <<38,82,66,195,32,104,237,43,83,159,129,35,147,210,127,111>>
# kw_cipher_text = <<190,44,54,51,249,251,181,74,97,136,146,214,135,87,185,96>>
# kw_cipher_tag  = <<239,96,255,0,14,148,163,150,121,52,49,245,171,61,134,3>>

## Decrypt
unwrapped_cek = :crypto.block_decrypt(:aes_gcm, kw_cek, kw_iv, {kw_aad, kw_cipher_text, kw_cipher_tag})
unwrapped_cek !== :error # => true
unwrapped_cek === cek    # => true
unwrapped_cek            # => <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>
decrypted = :crypto.block_decrypt(:aes_gcm, unwrapped_cek, iv, {aad, cipher_text, cipher_tag})
decrypted !== :error     # => true
decrypted === plain_text # => true
decrypted                # => "abcdefghijklmnop"

## Invalid Variables
# Mapping of verify_and_decrypt parameters to variables
#   secret      = bad_kw_cek
#   sign_secret = bad_kw_aad
bad_kw_cek = << 5 :: 256 >>
bad_kw_aad = << 6 :: 256 >>

## Decrypt with invalid secret and invalid sign_secret (should return :error)
unwrapped_cek = :crypto.block_decrypt(:aes_gcm, bad_kw_cek, kw_iv, {bad_kw_aad, kw_cipher_text, kw_cipher_tag})
unwrapped_cek !== :error # => false

## Decrypt with valid secret and invalid sign_secret (should return :error)
unwrapped_cek = :crypto.block_decrypt(:aes_gcm, kw_cek, kw_iv, {bad_kw_aad, kw_cipher_text, kw_cipher_tag})
unwrapped_cek !== :error # => false

## Decrypt with invalid secret and valid sign_secret (should return :error)
unwrapped_cek = :crypto.block_decrypt(:aes_gcm, bad_kw_cek, kw_iv, {kw_aad, kw_cipher_text, kw_cipher_tag})
unwrapped_cek !== :error # => false

References

  • Cisco: Next Generation Encryption

    [AES-GCM and HMAC-SHA-256] are expected to meet the security and scalability requirements of the next two decades

    [SHA-1] is recommended that these legacy algorithms be phased out and replaced with stronger algorithms

  • CloudFlare: Padding oracles and the decline of CBC-mode cipher suites

    This deals more with the TLS standard of MAC-then-encrypt versus Plug's encrypt-then-MAC behavior, but it's an interesting read.

    Most modern browsers and operating systems have adopted at least one AEAD cipher suite in their TLS software. The most popular is AES-GCM...

    It’s our responsibility to keep our customers secure by implementing the latest standards, like AEAD cipher suites.

  • Cryptosense: Choice of Algorithms in the W3C Crypto API

    AES-CBC mode is not CCA secure. It is secure against chosen plaintext attacks (CPA-secure) if the IV is random, but not if the IV is a nonce.

    It does not tolerate a padding oracle – indeed, in practice, padding oracle attacks are common and the padding mode suggested in the current draft is exactly that which gives rise to most of these attacks.

    “I am unable to think of any cryptographic design problem where, absent major legacy considerations, any of CBC, CFB, or OFB would represent the mode of choice.” — Phillip Rogaway

    [AES-GCM] mode has a security proof – the security notion is AEAD (Authenticated Encryption with Associated Data), which (loosely speaking) means that the encryption part is CCA-secure and the message and associated data are unforgeable.

    A procedure is known to obtain SHA-1 collisions in less than 262 operations.

  • chromium: Disable AES-256-CBC modes by default

    The chromium team wound up deciding to keep AES-CBC around for a little longer, but the final comment on the issue illustrates that it will be eventually removed.

    I would love to lose CBC mode ciphers, AES-258 and AES-128, but that's probably a ways out.

  • StackExchange: Practical disadvantages of GCM mode encryption

    The question is actually about the downsides of AES-GCM, but the accepted answer has a few points as to why GCM is preferable over CBC.

    CBC mode requires padding input to the block size, thus GCM mode produces smaller output if input is not multiple of block size.

    GCM is very good mode of operation, and it often is more convenient than legacy algorithms combinations like CBC + HMAC.

  • COSE: CBOR Object Signing and Encryption

    When using AES-GCM, the following restrictions MUST be enforced:

    • The key and nonce pair MUST be unique for every message encrypted.
  • JWA: Key Encryption with AES GCM (defines A128GCMKW, A192GCMKW, and A256GCMKW standards)

@josevalim
Copy link
Member

Wow! 👏 👏 👏

This is one of the best bug reports and pull requests I have ever received. Thank you for all the effort you have put into it.

AES-GCM mode is only supported in OTP 18+ and OpenSSL 1.0.1+. If there is any concern for supporting OTP 17 or older versions of OpenSSL, would it be better to switch algorithms based on whether support for AES-GCM is detected?

Plug master requires Elixir v1.2 which is OTP18+ so we are good on that front. I think switching algorithms can be dangerous because of nodes using different OpenSSL being suddenly incompatible so I would go with the best algorithm for now and see how it will work in the long term.

I chose AES128-GCM for the actual content encryption since the key is randomly generated and encrypted with AES256-GCM Key Wrapping. Would there be any preferences to use AES256-GCM for both operations?

Your choice as I am not familiar enough to decide so.

Are there any preferences for something other than HMAC SHA-256 as the default?

No.

Any issues or improvements to the design for future algorithm changes and backwards compatibility?

I will study the code and let you know. :)

defp decode_token(token) do
case String.split(token, ".", parts: 5) do
[protected, encrypted_key, iv, cipher_text, cipher_tag] ->
with {:ok, protected} <- Base.url_decode64(protected, padding: false),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave a TODO here so we use with/else once we depend on Elixir v1.3 only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

@josevalim
Copy link
Member

@potatosalad I have reviewed your changes and I have no further comment. Let me know when this is ready to merge from your side, thank you for the amazing work!

@potatosalad
Copy link
Contributor Author

@josevalim I've added the TODO comments about the with/else statements so I think everything else should be good to go!

@potatosalad
Copy link
Contributor Author

@josevalim I lied, I forgot to include a comment in Plug.Session.COOKIE about switching to verify/3 after the backwards compatibility period. Now everything should be ready to go.

@josevalim josevalim merged commit 5cb2f6c into elixir-plug:master Jul 7, 2016
@josevalim
Copy link
Member

❤️ 💚 💙 💛 💜

Gazler pushed a commit to Gazler/plug that referenced this pull request Oct 18, 2018
…r-plug#420)

* Replace AES-CBC with AES-GCM and HMAC-SHA-1 with HMAC-SHA-256.

* Remove reporting on failure for AES-CBC mode from tests.

* Add TODO comments about with/else statements in MessageEncryptor and MessageVerifier.

* Add TODO about switching to verify/3 after backwards compatibility period.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants