3
$\begingroup$

Let's say I have an API that provides an encryption wrapper around another API. I give the consuming programmer the option of either passing a string or byte array as an encryption key.

The byte array must be the correct size, and is used directly as the encryption key.

However, if they pass a string, I hash it using SHA256/SHA192/SHA128 before passing it to the encryption function.

Is it correct to hash text of arbitrary length before using it as a key?

Or should it be accepted as is, even if it means the encryption key will either have a large amount of \0 bytes if it is too short, or be truncated if it too long?


The underlying cipher is AES/Rijndael

$\endgroup$
  • $\begingroup$ Is it correct...? Correct according to whom? $\endgroup$ – mikeazo Apr 24 '18 at 13:45
  • $\begingroup$ Does hashing the key before using it create a security problem? I am 99.9999% sure that is a "no", but I am 100% sure there are plenty of people whom were sure that their stuff was secure, but were wrong... $\endgroup$ – TheCatWhisperer Apr 24 '18 at 14:27
  • $\begingroup$ Do you have a place to cache the derived key, like some sort of session? Or do you have to recompute it for every call? In the second case I'd suggest forcing the string to have the proper length. Suggested way to derive the key from string/password may be a part of documentation. $\endgroup$ – Frax Apr 24 '18 at 20:11
7
$\begingroup$

Considering that someone will probably end up supplying a password for that argument at some point, it would be better to use a proper slow key derivation function like pbkdf2/scrypt/argon2.

Even if they don't end up sticking a password in there, meaningful/arbitrary text and passwords should be treated the same in regards to an adversary's ability to guess the string. If the string is not actually a random value - meaning it's not the output of /dev/urandom - then it will not have 8 bits/byte of entropy, parts of it are guessable, and it should not be used directly as a key. Hashing an easily guessed value with SHA256 does not make the result unguessable - all it will do is make the output more appropriately sized.

Note

With that being said, if the option is available, you should prefer to use pre-existing crypto libraries such as libsodium. They have already handled all of these sorts of details (as well as all sorts of obscure pitfalls, side channels, and other black magic) and produced a succinct interface that more or less removes all of these sorts of questions for the developer.

$\endgroup$
  • $\begingroup$ Thanks for the great answer. Just wanted to clarify, the hypothetical library would leverage another cyrpto library, not use its own implementation of AES, ect. Also, they key would not ever be stored to disk (by the library itself). In fact, there would be an option to zero the key from main memory after its use. $\endgroup$ – TheCatWhisperer Apr 24 '18 at 14:18
  • 2
    $\begingroup$ This does not seem the proper place to try to insert a slow key derivation function. If code needs to perform many operations using the same key, using a slow KDF would greatly impair performance even in cases where it added nothing to security (e.g. because the strings had been produced in high-entropy fashion) and caching hashed strings to get around that would likely create a bigger security weakness than simply using a cheaper hash function. If it's necessary to have a slow KDF between user input and cryptographic usages, that should be taken care of on the client side. $\endgroup$ – supercat Apr 24 '18 at 18:33
  • $\begingroup$ @supercat I understand what you are saying, but I think that your comment should be directed towards TheCatWhisperer rather than myself. I think that including the string argument option in the encrypt interface as an alternative to a key is not a good idea at all, but it's not my project so I don't get to make those decisions. $\endgroup$ – Ella Rose Apr 24 '18 at 18:42
2
$\begingroup$

Let's say I have an API that provides an encryption wrapper around another API. I give the consuming programmer the option of either passing a string or byte array as an encryption key.

Encrypting with a proper random key and doing so with a password are different enough that I would opine that an API should strive to offer them as different operations, not as two modes of one operation. Moreover, the routine to encrypt with a password would want to bottom out to the key-based encryption, so code factoring calls anyway for there be a function for the latter.

How to implement this depends very much on your language's type system as well. For example, the Rust programming language—which is noted for having a well-designed static type system—the types you'd likely choose for a binary key and for a password would not be the same:

  • For a binary key you'd probably use a type like &[u8] (an unsigned byte slice (pointer + length into some array)) or &[u8; 16] (reference to a whole array of 16 unsigned bytes).
  • For a password you'd probably use &str, a string slice—pointer + length at a span of UTF-8 text).

By using a statically typed language that distinguishes at compilation time between binary data and text, such an API would therefore be much more likely to catch client errors at compilation time when a caller tries to pass a textual password to the raw encryption function or binary data to the password-based one. More generally, you've made it so that the path of least resistance is for the programmer to do the right thing.

In addition, the password-based API function shouldn't be implemented by simply hashing the password with one of the SHAs; it should use a dedicated password-based key derivation function like Argon2.

$\endgroup$

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.