5
$\begingroup$

I have a string secret that I would like to encrypt for storage in a Yubikey's second slot. These slots can be configured to store up to 38 characters that are later "typed" by the USB device acting as a keyboard. This means it can't store raw bytes, but fortunately it's sufficient for my use case for my secret to consist of 38 base64 characters.

Using a strong password, I'd like to use OpenSSL v1.0.1+'s implementation of aes-256-ctr to encrypt the secret for storage on the Yubikey. I am asking about using a counter cipher rather than an alternative because I need for the output to have the same number of bytes as the input. I also need to decode the input from base64 first so that it's using the bytes efficiently, and then encode it as base64 again after the encryption so that the result can be "typed".

echo -n "secret+that+is+exactly+38+base64+chars" | \
openssl enc -base64 -A -d | \
openssl enc -base64 -aes-256-ctr -nosalt -k "this will be a strong password"

Is this a secure way to store my secret? Would it be safe to, say, post the encrypted secret on the public Internet? I'm not planning to, but I also don't want to have to be constantly vigilant about the physical security of the Yubikey itself.


Edit: Thanks for the answers and comments! I apologize for having left some things ambiguous initially, but my lack of domain knowledge made it difficult to know what's relevant. Here are some clarifications:

  1. The Yubikey functionality I'm using is called Static Password mode. While the Advanced configuration for this mode can contain 64 modhex characters, the user cannot choose the password so this does not match my use case. The alternative is the Scan Code configuration which can store up to 38 ASCII 256 characters. I can select either US or DE keyboards in the Yubikey Personalization Tool that I'm using to configure the device; as I understand it, this determines the scan codes that are emitted to correspond to my saved password, and a US keyboard will work for me. I realize this is 4x the different characters I get from base64, but since I was able to get something that seemed to work as-is, I didn't try to convert the encrypted bytes to ASCII 256 for more efficient storage. Links: Yubico knowledge base, PDF documentation (see page 25), scan code explanation.
  2. The secret I'm planning to encrypt and store is a 1Password Secret Key, so it will have 128 bits of entropy. (I removed some dashes to make it fit the 38 characters, and converted the remaining dashes to pluses.) I'm trying to set up a recovery plan that is more accessible than a piece of paper in a safety deposit box, and more secure and durable than a piece of paper in my wallet. I omitted this detail from my original question because it seemed like a distraction, but I see now it's relevant to certain types of attacks.
  3. I was planning to use a proper diceware password with at least seven words. If I use ten for 128 bits, is my original proposal sufficient?
  4. I'd love to find a way to make this work securely with the standard tools in OpenSSL. Part of the reason for this is that other tools (such as argon2) seem a bit over my head / beyond what I can trust myself to use properly. Most of the reason, however, is that I want to use tools that are at least somewhat universally accessible so that I'm able to decrypt my 1Password Account Key and recover from hardware loss in the widest variety of possible circumstances (i.e. if I'm abroad without my laptop). One appealing thing about OpenSSL ciphers is that there are Javascript implementations that I could use on a trusted iPhone to do the decryption, obviating the need to install and use OpenSSL on some unknown machine. I was able to get the above encryption/decryption to work using Forge, for example.
$\endgroup$
  • $\begingroup$ So, just to be clear, you want to store something in 38 "bytes" which can only be typeable characters? $\endgroup$ – Elias Oct 25 '17 at 14:54
  • $\begingroup$ And how does this yubikey function even work, does it depend on the used keyboard layout? $\endgroup$ – Elias Oct 25 '17 at 14:54
  • $\begingroup$ @Elias Thanks for the questions, and I've edited the question to clarify how the Yubikey works in #1 above. $\endgroup$ – lehrblogger Oct 26 '17 at 5:32
4
$\begingroup$

This is insecure if more than a single secret is processed with the same password. Plus, OpenSSL has abysmally insufficient key stretching in its processing of the password, making brute force password search a concern.

The first (and worse) problem is because AES-CTR is a stream cipher, and thus malleable; and with no IV (-nosalt) it reduces to bitwise exclusive-OR with a key-dependent value. Knowing any secret $S_i$, the encrypted secret $E_i$, and another encrypted secret $E_j$, allows to find the other secret using $S_j=E_i\oplus S_i\oplus E_j$ (where $\oplus$ is bitwise exclusive-OR with arguments converted from Base64, and the result converted back to Base64).

Insecurity extends to changing a secret: knowing the old secret, its encrypted form before and after change, the new secret is revealed.


I have checked for key stretching built into OpenSSL enc in openssl-1.1.0f, that is the process by which the argument to -k is turned into an AES-256 key. What I found is not pretty. The man page rightly says "There should be an option to allow an iteration count to be included". If I did not got lost in the maze of C code with terse variable names and few helpful comments, there's an iteration count permanently set to 1 and in the case at hand, the AES key ends up as the SHA-256 of the argument to -k. That other answer is right to point that a better password hashing scheme is missing.

In addition, usual warnings apply: the argument to -k must be kept secret, and of course the hardest: it must be entered only on machines trusted from a hardware and software perspective.


Ah, a boring detail: openssl enc -base64 -A -d processes its input by multiple of 4 characters, and will ignore the last 2 characters of its 38-character input; correspondingly, the output of the script shown is only 36 characters. I see no fix to get the full 38-character capacity asked.


What's really needed is a 228-bit block cipher with a tweak (the slot index in the Yubikey), and a conversion of password to key with modern key stretching. Perverting openssl enc do that would be extremely hairy.

$\endgroup$
  • 1
    $\begingroup$ AES is not always malleable but AES-CTR is. The (very poor, as you say) PBKDF in openssl enc defaults to SHA256 in 1.1.0 and MD5 in lower versions (1.0.2 is still supported upstream, and even lower by some distros) but can be changed (which doesn't improve it). For details see my answer at crypto.stackexchange.com/questions/3298/… $\endgroup$ – dave_thompson_085 Oct 26 '17 at 4:26
  • $\begingroup$ Thanks @fgrieu. I think that detail you found explains some trial and error I went through with padding, but the encryption+decryption round trip seems to work as-is, so I think it's ok. $\endgroup$ – lehrblogger Oct 26 '17 at 5:32
  • $\begingroup$ Fortunately I can be sure to use a new password each time I want to rotate the secret. I've also given some thought to machines that I can trust from both hardware and software perspectives, which I'll elaborate on in #4 above. $\endgroup$ – lehrblogger Oct 26 '17 at 5:32
  • $\begingroup$ @lehrblogger: Base64 is such that unless you have a multiple of 4 characters, there will be information lost in the end. Your script eats the last 2 characters in its input, and padding with 2 extra characters or == then removing them will only reliably get you to 37 characters, not 38. On the other hand if rightmost characters are known to be +, then OK. Changing the password at each secret change is a step in the right direction. Another is to append the index in the Yubikey to the password (poor's man tweak), solving the problem in the second paragraph of the answer, currently the worse. $\endgroup$ – fgrieu Oct 26 '17 at 7:10
  • $\begingroup$ @fgrieu Sorry for the delay – somehow I missed this comment. You're right about base64 requiring multiples of 4 characters – I think I had miscounted the characters originally, sorry! That explains some of the padding behavior I was struggling with initially. Fortunately, 36 characters is sufficient, since I can just remove more of the dashes from the 1PW Secret Key. What do you mean by the slot index in the Yubikey? Would a mode other than CTR be better? $\endgroup$ – lehrblogger Nov 27 '17 at 6:02
3
$\begingroup$

No.

AES256 (and all other block ciphers) are as good as their key. Keys are are only allowed to be fully (uniformly) random. Your key is not random, and has a very low entropy, so the security of AES256 will be reduced as well.

A rough estimate of the security of your key (and thus your secret) would be to estimate the entropy of the key. If we assume the attacker only knows the format of your key, namely six English words, surrounded with +'s, then your key will have 66 bits of entropy. For a normal security I would always use a security level of (at least) 128 bits. 66 bits is way too low.

A better option is to use a secure password hashing scheme like argon2 with a good unpredictable passphrase and enough iterations, and use the generated key for the AES256 encryption.

$\endgroup$
  • $\begingroup$ Maybe his example was not supposed to display the structure of the secret. $\endgroup$ – Elias Oct 25 '17 at 14:45
  • $\begingroup$ Even if it did I don't see a problem with this assuming that k is a good key. $\endgroup$ – Elias Oct 25 '17 at 14:49
  • $\begingroup$ If he uses a normal 32 character password as the key, I don't really think I can make this assumption. $\endgroup$ – dsprenkels Oct 25 '17 at 18:38
  • $\begingroup$ @DaanSprenkels @Elias Thanks for the answers/comments. I'm a little confused if the "key" you're referring to is my secret (based on the comments around the "six English words, surrounded with +'s") or my password (based on everything else). I'm pretty sure it's the latter, but regardless, I've tried to respond to the former in #2 above, the latter in #3, and the argon2 suggestion in #4. $\endgroup$ – lehrblogger Oct 26 '17 at 5:33

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.