Objective: To have a very simple to verify, but very secure small message encryption.

Idea: Messagebcrypt( Password). We remove password biases with bcrypt and then XOR resulting key with the Message.


Limitation: Only messages up to 320 characters long are supported.

10 bcrypt salt values are hard coded:

  • Salt1 = $2a$12$geKv/Jdb2zSGEtnn3AuXXe
  • Salt2 = $2a$12$h1V42zCLf/F5F7RbueVuZe
  • Salt3 = $2a$12$UTX8OMvg1CI3/pnbT0F5Mu
  • . . . . . . .
  • Salt10 = $2a$12$Yq9Vo0yefk6zJlEvJmBf6O

Suppose we have

Message=My safe code is 29540323, my bank card pin is 6926

Password= 32GateKeepers

The message is 50 characters long. bcrypt produces keys 32 characters long. So we need 2 keys to match message length:

Key1 = bcrypt(Password, Salt1) = e8.4zyJT9m9R/KX./Kd2juSHn25IqGUO

Key2 = bcrypt(Password, Salt2) = emTDJsUVCLFE3Jm8Cjx2S2/WmXEVrrU.

Key (50 characters) = e8.4zyJT9m9R/KX./Kd2juSHn25IqGUOemTDJsUVCLFE3Jm8Cj

Code = Message ⊕ Key = 40,65,14,71,27,31,47,116,90,2,93,55,15,34,43,14,29,114,81,6,90,70,97,123,66,18,88,48,81,37,52,33,14,77,55,37,56,23,117,38,42,34,102,44,64,106,91,1,113,92

Question: Is bcrypt de-biasing good enough to use in XOR encryption?

Specific Problem: Attacker knows that the same password was hashed with ten known salts for longer messages.

Specific Question: If the attacker has 10 hashes of the same password with 10 known salts. Given the nature of hashes, does this weaken the password?

  • 6
    $\begingroup$ You realize that you can only use this encryption once, right? And that it provides no integrity protection. $\endgroup$ – Elias Jun 28 '17 at 14:19
  • $\begingroup$ Good point. I had to put it as a limitation. The encryption is intended to secure a single high value message with never to be re-used password. You are right again about the integrity, I should think about it if main idea holds. $\endgroup$ – Evgeny Jun 28 '17 at 15:43

Any time you perform more than one evaluation of a password-hashing algorithm where it is a legitimate option to perform it only once you are doing something wrong. Here is why:

  • If one hash evaluation costs $C$, then 10 hashes with with the same cost-parameter will costs $10 \times C$.
  • Hashes can be evaluated by a cracker with a cost per candidate-password proportional to $C$. (Probably a lot less than C since they can build or buy systems optimized for and dedicated to password cracking.)
  • Given $x = H(\text{salt}, \text{password})$, one can guess a candidate password $p$ and compare $x$ to $y = H(\text{salt}, p)$.
    • If $x \neq y$ then $\text{password}$ is not $p$.
    • If $\text{password}$ is not $p$ then $x = y$ with only $1 / N$ probability where $N$ is the size of a hash's image set. *
  • Given knowledge of some $n$-bit long subset of one hash output's bits, a cracker can similarly evaluate the hash of one candidate password with a cost $\le C$ with a $2^{-n}$ false positive rate.
    • If the adversary knows one character of plaintext (8 bits), then out every 256 candidate passwords, 255 candidates can be eliminated with work proportional to $C$. Only 1 out of every 256 will need two or more hash function evaluations to check for false positives.
    • Because each 32 byte block can be tested independently, the password cracker will rarely have to do proportional to $10 \times C$ work but the legitimate password-holder will always do $10 \times C$ work,
    • The cracker has a significant advantage over the honest party compared to an alternate method: Use a single password hash evaluation (a password based key derivation function) to calculate one key and use that key with a typical symmetric key message encryption algorithm.

One can put key derivation functions (which you can think of as hash functions used to deterministically generate keys) into two categories: password-based KDFs and key-based KDFs.

"Password hashes" (or "slow hash algorithms" as non-experts sometimes say) are used to describe password-based functions that "stretch" passwords. Passwords are usually low entropy (that is they're relatively predictable) and so password hashes and PB-KDFs are designed to be artificially slow for both legitimate parties and password crackers to evaluate with the hope that it makes password cracking less "profitiable".

Standard or "fast" hash algorithms are not necessarily insecure just because they are fast. If passwords were as unpredictable as a 128 bit key then password bases could just store SHA-2 hashes of passwords (preferably still salted for off-topic reasons) with no loss to security.

The right way to do things is to increase your password hashing algorithm's cost parameter by a factor of ten. You're doing the same amount of work, $$1 \times (10 \times C) = 10 \times (1 \times C)$$ so your CPU-time budget doesn't increase. On the other hand, the adversary, with only one slower hash function to crack, now needs to always do proportional to $10 \times C$ work instead of just $C$ work.

If you need more key material than a PB-KDF can give you without multiplying the amount of work you need to do, then derive just one master key from the PB-KDF and feed the derived key to a KB-KDF. You can derive many keys from a key-based key derivation function much faster than the run time of a password hashing function.

If for some reason you must use only hash algorithms instead of a stream cipher, then do $$K_M = bcrypt_{10 \times C}^\text{**}(\text{salt}, \text{password})$$ $$K_i = \text{SHA-512}(K_M \| i \| \text{nonce})$$ $$\text{ciphertext}_i = \text{plaintext}_i \oplus K_{i + 1}$$

And send $$\text{ciphertext} \| \text{HMAC-SHA-512}(K_0, \text{ciphertext})$$ because you need message authentication in addition to message secrecy.

In my previous cost analysis I treated the key-stream computation as if it were zero. If you choose a large $C$ then this is a reasonable assumption. Otherwise assume little $c$ is the cost of a "fast" hash. You CPU cost is $10 \times C + 13 \times c$. (Since HMAC is 2 more hashes and you derive one more key for HMAC.) And the adversary's cost is $10 \times C + c$. But both are approxmately $10 \times C$.

For your specific proposal, assuming you're using ASCII encoding of plaintext, you can easily infer 32 bits of every full 32 byte block because no standard ASCII character has a byte value with the most significant bit set.

For other types of plaintext it won't necessarily be so trivial, but you should assume that there are some other ways to guess at plaintext. If there is uncertainty then the adversary could just compare more plaintext-guesses to the same hash evaluation. You get more false positives obviously, but this doesn't prevent cracking. False positives can be distinguished from true positives by evaluating a hash with each salt then looking at the decryption to see if it makes sense. (Check if it contains real dictionary words, or do statistical tests comparing it to reasonable plaintext.)

* Assuming the hash function is unbiased, which is reasonable for good password hash algorithms and many common not-good ones. (SHA-1 and MD5 included among other bad ones.) For the plain-old password hashing use case (not your use) false positives (which are so improbable that it's safe to say they won't happen) are just as good as true positives.

** Use Argon2d if an optimized implementation is available instead of bcrypt. bcrypt is fine if it has an optimized implementation. Same for PB-KDF2 and balloon hashing. (Don't request PB-KDF2 output length that is too long.) You can use scrypt as well if there is an optimized implementation and if you can give it a large CPU and RAM allowance. ... I'm beginning to see that people hear the "Use ___ instead of ___ for password hashing" advice that's beginning to reach a larger audience now and are taking away the cargo-cult-like idea that it means "Always substitute [password hashing function] for [normal hash algorithm or encryption algorithm]". That's wrong. Only use password hashing functions for password storage and for derivation of a master key.

Edit: Argon2d. not Argon2i.

  • $\begingroup$ Most encryption schemes are "broken" to: iterate over the potential list of passwords checking against a known bit of the plaintext. There are mitigations and this one is particularly weak, but it doesn't make it wrong I think @PaulUszaks answer of technically maybe but please don't, or Elias's comment of sure if you only ever want to use it once, are better ways to think of this. $\endgroup$ – ANone Apr 29 at 13:27

Is BCrypt de-biasing good enough to use in XOR encryption?

With extreme narrow mindedness, the technical answer is yes it is. It will produce a uniformly distributed random output can can be used in a short stream cipher. All decent password stretching /key derivation functions can as they use cryptographic primitives like HMACs or parts of them like Blowfish. So yes.

Rather than jump into a technical analysis of your scheme, I'd say that overall this is a very unorthodox method to secure short messages. You're taking on the role of Frankenstein creating a stream cipher monster out of parts that were never intended to be put together. Yes, it might be alive, but it's also horrible.

Consider that BCrypt output is alphanumeric rather than binary so you'll have to perform modular xorations(?). And longer messages require more keys. Kerckhoffs's principle warns tin foil wearing types that all the salts will be known, so they become irrelevant to the overall security context. And your final cipher text will be malleable as all un authenticated ciphers are. This might not be an issue in your case though.

There are common encryption methods based on stream ciphers than will automatically generate a pseudo random stream for subsequent xor. Or block ciphers. It doesn't really matter internally does it?

We remove Password biases with BCrypt and then XOR resulting Key with the Message.

You have to think very carefully. What are you creating? Password text biases are automatically resolved in standard symmetric key methods. There is even original Blowfish if you like, and that's with 400+ bit keys. Debiasing is definately not BCrypt's raison d'être. If anything, debiasing is straying into areas of entropy and it's extraction.

I'm a huge fan of rolling your own simple encyptions but I think that in this case you need to find some flaming torches, organise the villagers and kill this thing.

If the attacker has 10 hashes of the same password with 10 known salts. Given the nature of hashes, does this weaken the password?

This actually forms a good question, but might be better spun off as a separate one. This will encourage answers unbridled with the odd cryptographic monster baggage.


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.