Sunday, December 15, 2013

Crypto Fails has Moved!

Crypto fails has moved to Tumblr.

New posts will only be posted there. This means the email subscription thing will break, too, so if you're using that, subscribe to the tumblr feed or follow it on tumblr.

Thanks :)

Saturday, December 14, 2013

Most Android Apps are Crypto Fails

This study from Carnegie Mellon and UCSB analyzed 11,748 android apps that use crypto and found that 10,327 of them (88%) were flawed. They built a tool to check for extremely obvious crypto implementation errors like
  • Using ECB mode.
  • Using a non-random IV for CBC mode.
  • Using constant encryption keys.
  • Using constant salts for password hashing.
  • Using fewer than 1000 iterations in password hashing.
  • Seeding the random number generator with a static value.
Except for the "1000 iterations" one, these are all obvious flaws, and anyone who knows anything about cryptography should know that they are a bad idea. Especially "using constant encryption keys" - that's insane.

Anyway, here are their results summarized in a table.


Their results make two things clear:
  • You shouldn't implement crypto yourself. Even when you have a high-level API.
  • Just because an app "uses military-grade AES encryption", that does not mean it is secure. It probably isn't.

Crypto Noobs #2: Side Channel Attacks

What are side channel attacks and how do they affect cryptography?

Suppose your birthday is coming up soon, and your best friend told you that they bought a gift for you. You're anxious to know what they got you, so you ask them:

"Is it a new watch?"
    "No."
"Is it a hat?"
    "No."
"Is it a computer?"
    "No."
"Is it a book?"
    "No."
"Is it a video game?"
    "No."

You're not getting anywhere. Your friend will say "No." no matter what you guess, even if you guess right. You need another source of information. You try asking again, but this time you pay close attention to their actions as they reply:

"Is it a new watch?"
    "No." (expression=neutral, eyes=looking at you)
"Is it a hat?"
    "No." (expression=neutral, eyes=looking at you)
"Is it a computer?"
    "No." (expression=neutral, eyes=looking at you)
"Is it a book?"
    "No." (expression=nervous, eyes=looking away from you)
"Is it a video game?"
    "No." (expression=relief, eyes=looking at you)

Now can you guess what your gift is? From these results, you can be pretty sure that your gift is a book. If you want to be even more sure, you can ask the questions again. If your friend's expression and eye movements are always changing after asking, "Is it a book?" you can be pretty sure that's what it is.

That's a side channel attack. You're getting no information from the actual data in the response ("No."), but the way the response is delivered is leaking the secret information you want.

Computers don't make side channel attacks quite so obvious. They don't have faces to convey emotions. So how is the information leaked in a side channel attack against a computer? There are a bunch of different ways:
  • How much power the computer uses when it does something.
  • How long it takes the computer to do something.
  • Which areas of the computer's memory have been accessed.
  • Unintentional electromagnetic radiation emanating from the system.
  • Sounds coming from the system (beeps, hard drives working, etc.).
  • The time that network packets get sent out of the system.
These are some of the ways a computer can unintentionally leak information about what it is doing and the secrets that are stored inside of it.

This leakage can be devastating to cryptography software. Even if the cipher is secure, there are no attacks against the protocol, and the implementation follows the specification exactly, a secret might still be leaked out through these avenues, which we call "side channels."

The next sections present some real examples of side channel attacks. They should give you a good idea of how devastating these attacks are on cryptography software.

Timing Analysis of Keystrokes inside SSH

This attack was presented in Timing Analysis of Keystrokes and Timing Attacks on SSH by Dawn Xiaodong Song, David Wagner, and Xuqing Tian.

Secure Shell (SSH) is a secure way of connecting to another computer and controlling it with a shell (terminal). All of the commands and responses are encrypted and authenticated, so someone eavesdropping on your Internet connection should not be able to figure out what you're doing on the remote system.

The ciphers are good, the protocols are good, and the implementation is good. Even so, the trio of researchers realized that information about what the user still gets leaked, through the time between the user's key presses.

When connected to a computer over SSH, every time you press a key, your computer immediately generates an Internet packet and sends it to the remote computer. That means, if you're eavesdropping on an SSH connection, you see a packet sent shortly after each key press. You don't know which keys were pressed, but you know what times they were pressed, and, more importantly, the times between key presses.



The researchers showed that, when a user is typing, the time between their key presses leaks information about what they're typing. For example, on a QWERTY keyboard, the time between pressing "a" then "j" (which is on the home row and is pressed by the index finger of the left then the right hand) is going to be a lot shorter than the time between pressing "z" and "1" (which is pressed by the same finger and the keys are very far apart).

Using this fact, the researchers constructed an attack tool that extracts about 1 bit of information per character pair when a user is typing a password. This means that if you can observe an encrypted SSH connection, and the user types a password, the information leaked through the keystroke timings greatly reduces the number of passwords you'd need to search to find the right password.

To remove this side channel, SSH should send packets at a fixed rate, even if the user is not typing or is in between key presses. If a packet gets sent every 50 ms no matter what, and there is no way to distinguish a keystroke packet from a "chaff" packet, then the eavesdropper won't learn anything about the time between key presses.

Cache Attacks on the AES Cipher

This attack was presented in Cache Attacks and Countermeasures: the Case of AES by Dag Arne Osvik, Adi Shamir, and Eran Tromer. A similar attack was demonstrated by Daniel J. Bernstein in Cache-timing attacks on AES.

All modern computers have a "cache" between main memory (RAM) and the CPU. This cache speeds up access to areas of memory that have been used recently. If an area of memory has been accessed recently, it's probably in the cache, so accessing it again will be quick. If the area hasn't been accessed in a long time, it has probably been evicted from the cache, so accessing it will be slow. The difference in time can leak information about what a process is doing.

This attack was applied to the AES cipher in the two papers linked above. Essentially, fast implementations of AES use lookup tables, which are arrays used to quickly convert one value into another. The indexes AES uses into these tables depend on the secret key, so there's a possibility that the difference in memory access time caused by the cache can leak information about the secret key. That is exactly what the authors demonstrated.

The authors demonstrate two different techniques for extracting the key. I'll give a simplified explanation of each.

In the first, called the Evict+Time attack, they evict part of one of the lookup tables from the cache, so that all of the AES lookup tables are in the cache except for one index in one table, then they run the encryption. If the encryption accesses that index that has been evicted from the cache, it will run slower, otherwise it will run at a normal speed. So, by timing how long the encryption takes, they can figure out if that table index was accessed or not. Since the table index depends on the key, this leaks information about the key.

The second type of attack, called Prime+Probe, first completely fills the cache with the attacker's data. The encryption process is run, and as it is running, the parts of the lookup table that it uses are loaded from main memory into the cache. Since the cache is full of the attacker's data, some of it will have to be evicted to make room for the part of the table. Once the encryption is done, the attacker accesses their data again (to see which parts have been evicted from the cache), and this tells them which table indexes were used by the encryption process, leaking information about the key.

There is no good cross-platform cross-architecture defense against this kind of attack. The only sure defense is to write code whose memory access pattern does not depend on secret information. That means, among other things, no using secret information as indexes into an array.

Power Analysis of RSA

This attack was presented in Power Analysis Attacks of Modular Exponentiation in Smartcards by Thomas S. Messerges, Ezzy A. Dabbish, and Robert H. Sloan.

RSA is a public key cryptosystem. Encrypting a message involves raising it to the power of the secret key. This is done using an algorithm called "square and multiply."

Here's a simple way to think about it: Loop over all bits in the secret exponent, and for each bit, if it is one, perform a multiply operation then a square operation. If the bit is zero, do not perform the multiply, just go straight to the square operation.

It turns out that by watching the amount of power a device uses, you can determine whether the multiply routine executed or not. That leaks the corresponding bit of the secret exponent. If multiply was executed, the bit is 1, if multiply was not executed, the bit is 0. So, by watching the power usage, you can extract the entire secret key. Here's what it looks like in a simulation:


This is obviously a devastating attack, since it leaks the entire secret key. To defend against it, you have to make sure that the amount of power your code uses doesn't depend on secret information.

Noise Floor

At DEF CON 21, @0xabad1dea gave a talk in which she demonstrates how you can use cheap (~$15) software defined radio hardware to learn information about a (very unshielded) system, including getting a (very) rough view of what's being shown on the monitor.



Conclusion

There you have it. That's only a very small sample of the side channel attacks that have been found. I would love to have written about more, especially this one, but I don't want this post to go on forever.

Side channel attack research is still very active. If you're interested, check out some of the papers I've linked to and the papers that they cite. You can even try to implement some of the attacks yourself.

What is "Crypto Noobs"?

"Crypto Noobs" is a series of posts where I answer your crypto questions. Each month, I will select one question and answer it in the next "Crypto Noobs" post.

Please send me your questions. All question askers will remain anonymous, so don't be afraid to ask dumb questions.

Saturday, November 30, 2013

CryptHook: Encrypting and Authenticating Network Traffic

CryptHook is a tool that hooks the send() and recv() system calls to apply encryption to a network application that does not provide encryption. The code is available here.

It uses the same key for both directions of communication. Traffic flowing from the client to the server is encrypted with the same key as the traffic flowing from the server to the client. It tries to use GCM to provide message authentication, but unfortunately, since it uses the same key for both directions and doesn't use sequence numbers, it's possible to:
  • Replay a party's messages back to itself.
  • Re-order messages.
  • Selectively drop messages.
It also derives the keys from a password, and does not exchange a session key, so there is no forward secrecy.

This one isn't so bad, especially given the environment it's operating in, but it's a good reminder that encrypting network traffic is extremely hard, and it's much better to stick to something like TLS or an OpenSSL VPN.

Thursday, October 10, 2013

Crypto Noobs #1: Initialization Vectors


What are Initialization Vectors and how should they be exchanged?

tl;dr: The Initialization Vector (IV) is an unpredictable random number used to make sure that when the same message is encrypted twice, the ciphertext always different. It should be exchanged, in public, as part of the ciphertext.

We don't want the ciphertexts to be the same when we encrypt the same message is because it leaks information. Suppose Alice is asking Bob a series of "yes or no" questions, and that the crypto software they're using makes the mistake of always encrypting the same message to the same ciphertext.

Here is their encrypted conversation:

Alice: DCF0C50A96DC2D9B05F2AC4C24CB9B93
Bob: E0B554B341FF5632DE241FBF4B1DBB37
Alice: 6742FA9512C8C2ACE6942974C8C848FC
Bob: 824783C3B272FF7129F9E153EC10D1AE
Alice: C90BD345639368D951A8B5E267427514
Bob: E0B554B341FF5632DE241FBF4B1DBB37
Alice: AC797939F55E87C361A8F02B4CEA1A08
Bob: 824783C3B272FF7129F9E153EC10D1AE

As you can see, some of Bob's ciphertexts repeat. The ones that are the same are highlighted in red and green. Since we know that Alice is asking Bob "yes or no" questions, we can assume that one of the ciphertexts corresponds to "yes" and the other to "no."

Now, suppose we ask Alice what questions she asked Bob. Alice tells us she asked the following questions (in order):
  • Are you Bob?
  • Are you Eve?
  • Are you Smart?
  • Are you Tired? 
Note: Alice tells us what she asked Bob, but not what Bob's answers were.

If Bob answered the questions truthfully, then we find out that Bob's first response, the ciphertext "E0B55..." is the encryption of "yes" and Bob's second response, the ciphertext "82478..." is the encryption of "no". So "E0B55..." is "yes," and "82478..." is "no." Now we know what Bob's answers to the last two questions were:

Alice: DCF0C50A96DC2D9B05F2AC4C24CB9B93 <- Are you Bob?
Bob: E0B554B341FF5632DE241FBF4B1DBB37 <- yes
Alice: 6742FA9512C8C2ACE6942974C8C848FC <- Are you Eve?
Bob: 824783C3B272FF7129F9E153EC10D1AE  <- no
Alice: C90BD345639368D951A8B5E267427514 <- Are you Smart?
Bob: E0B554B341FF5632DE241FBF4B1DBB37 <- yes
Alice: AC797939F55E87C361A8F02B4CEA1A08 <- Are you Tired?
Bob: 824783C3B272FF7129F9E153EC10D1AE  <- no

So Bob is smart, but not tired. We can probably assume he's angry that we cracked his encryption, too.

To fix this problem, we have to make sure that encrypting the same message always results in a different ciphertext. We have to make sure that when Bob answers "yes", it encrypts to something different than the last time he answered "yes".

This is accomplished using either an Initialization Vector (IV) or a nonce.

An Initialization Vector is an unpredictable random number used to "initialize" an encryption function. It has to be random, and an adversary shouldn't be able to predict it before the message is encrypted. See here to learn why it needs to be unpredictable.

The term "nonce" comes from "number used once", which is exactly what it is. It's a unique number. It can be random, but it doesn't have to be. As long as it's unique (never used before).

IVs and nonces are used by encryption modes like CBC and CTR to make all plaintexts encrypt differently. The ciphertext is a function of the plaintext and the IV or nonce. When the same plaintext is encrypted many times, the IV or nonce will be different each time so the ciphertext will be different each time.

IVs and nonces do not have to be kept secret. They are usually prefixed to the ciphertext and transmitted in full public view.

IVs should be generated by a cryptographically-secure random number generator, and not derived from the secret key. CakePHP made the mistake of using the key as the IV, making its encryption very easy to crack.

What is "Crypto Noobs"?

"Crypto Noobs" is a new series of posts where I answer your crypto questions. Each month, I will select one question and answer it in a new "Crypto Noobs" post.

Please send me your questions. All question askers will remain anonymous, so don't be afraid to ask dumb questions.

Saturday, August 10, 2013

Very Bad Password Advice

This post on How-To-Geek about generating passwords from the command line advocates generating passwords from the current date and time.


The first command they give is (note double fail: base64-encoding hex):
 date +%s | sha256sum | base64 | head -c 32 ; echo  
They do provide a command that gives a good alphanumeric password:
 tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1  
 However, they also mention this one:
 date | md5sum  
 About the above command, they say, "I'm sure that some people will complain that it’s not as random as some of the other options, but honestly, it’s random enough if you’re going to be using the whole thing." That is absolutely wrong and demonstrates a complete lack of understanding what a hash function is.

Lessons Learned:
  • Hashing does not add randomness. The output of a hash is as random as its input.
  • Use a cryptographically-secure random number generator to generate passwords. 

Monday, July 29, 2013

PHP Documentation Woes

This bit of php documentation has some interesting code:

 <?php  
 $passphrase = 'My secret';  
   
 /* Turn a human readable passphrase  
  * into a reproducable iv/key pair  
  */  
 $iv = substr(md5('iv'.$passphrase, true), 0, 8);  
 $key = substr(md5('pass1'.$passphrase, true) .   
         md5('pass2'.$passphrase, true), 0, 24);  
 $opts = array('iv'=>$iv, 'key'=>$key);  
   
 $fp = fopen('secret-file.enc', 'wb');  
 stream_filter_append($fp, 'mcrypt.tripledes', STREAM_FILTER_WRITE, $opts);  
 fwrite($fp, 'Secret secret secret data');  
 fclose($fp);  
 ?>  

3DES takes a 192-bit key (actually 168), so the first two keys are taken from the first MD5 and the third key is taken from the second MD5. I have a strong suspicion that that alignment introduces some kind of vulnerability, but I can't quite put my finger on it. If anyone knows, please leave a comment.

This tweet says it all:

Lessons Learned:
  • Don't use MD5 to derive keys from passwords. Use PBKDF2.
  • Use a random, unique IV. Don't derive it from the password.
  • Documentation will be used by many people. It's worth hiring a cryptographer to check it.

Thursday, July 25, 2013

Password Hashing Fail

Here's a bit of password hashing fail.

I'm not sure if it will win the Password Hashing Competition, but it certainly is novel. I really like how, after it goes through all of that sha1/md5/rot13 "encryption", it saves the plaintext to the database. Oh, and if it weren't for a misplaced '}', it would accept any password in the database, not just the one for your account. It's also vulnerable to SQL injection (pointed out by an Anonymous commenter).

Hashing a password:
 public function encryptData($string)
 {  
   $stringORG = $string;  
   $string = str_rot13($string);  
   $string = md5($string);  
   $string = substr($string, 5, 15);  
   $string = sha1(SALT . $string);  
   $string = md5($string);  
   $string = sha1(SALT . $string);  
   $string = str_rot13($string);  
   $string = substr($string, 5, 15);  
   $string = md5(SALT . $string);  
   mysql_query("INSERT INTO `encryptions` (`encvalue`, `realvalue`) VALUES ('".$string."', '".$stringORG."');");  
   return $string;  
   }  
Testing a password:
public function tryEncryption($tried)
{  
  $query = mysql_query('SELECT * FROM encryptions');  
  while($row = mysql_fetch_assoc($query))  
  {  
    $encVal = $row['encvalue'];  
    $actVal = $row['realvalue'];  
  }  
    $actTried = $tried;  
    $tried = str_rot13($tried);  
    $tried = md5($tried);  
    $tried = substr($tried, 5, 15);  
    $tried = sha1(SALT . $tried);  
    $tried = md5($tried);  
    $tried = sha1(SALT . $tried);  
    $tried = str_rot13($tried);  
    $tried = substr($tried, 5, 15);  
    $tried = md5(SALT . $tried);  
    if($encVal==$tried)  
    {  
      if($actVal==$actTried)  
      {  
        return true;  
      }  
    }  
}  

Lessons Learned:
  • When you encrypt something, make sure the plaintext is safely destroyed.
  • Hash passwords properly.
  • Write unit tests for your code.

Monday, July 15, 2013

PHP-Encrypt-Decrypt: Binary vs. Hex

This PHP script makes an interesting mistake. It attempts to derive a key from a password using md5, but it uses the hex-encoded MD5 output as the key, not the raw binary output. The hexadecimal string is passed to the mcrypt_encrypt() function, which interprets the 32-character hex string as a 256-bit key, even though it's really only 128 bits.

The author's intention was probably to use AES. In PHP, MCRYPT_RIJNDAEL_256 refers to the 256-bit block variant of Rijndael, not the 128-bit block version that was selected for AES. The key size of 128, 192, or 256 bits is determined by the length of the key parameter.

 $SECRET_KEY = 'YOUR SECRET KEY';  
 // ...  
 $encrypted_i = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($SECRET_KEY), $value, MCRYPT_MODE_CBC, md5(md5($SECRET_KEY))));  

Lessons Learned:
  • Specify crypto parameters in the right format. Look at the output of the functions you use to make sure it's in the format you expect.
  • Use key stretching (PBKDF2) to derive a key from a password, not MD5.
  • Generate a random IV at the time of encryption. Do not derive it from the key.
  • Use test vectors to ensure you are using the correct primitives.

Saturday, July 13, 2013

PHP-Security-Library: $ciphertext->key

It's generally not a good idea to store the key in your ciphertext object. If you do, naive users are going to think it's ok to store the key with the ciphertext, and advanced users are going to serialize() it all by accident.

 use \PSL\Encrypter;  
 $confidentalText = 'I am using PSL';  
 $cipherText = Encrypter::encrypt(MCRYPT_RIJNDAEL_256, $confidentalText);  
 // $cipherText is an instance of \PSL\CipherText.  
 // To extract, do something like this:  
 $key = $cipherText->key;  
 $plainText = Encrypter::decrypt($cipherText, $key);  
 // $plainText is now same as $confidentalText.  

Lessons Learned:
  • Do not store the key in the ciphertext.
  • Design APIs so that it is as difficult as possible to use them incorrectly.

Friday, July 12, 2013

Password Generators: Math.random()

When you do a Google search for "password generator", you find a lot of JavaScript-based password generators. Unfortunately, all of the high-ranking ones use Math.random(), which is a pseudorandom number generator unfit for cryptographic use. In Firefox and other browsers, Math.random() is seeded with the time and provides at most 41 bits of entropy. The JavaScript-based generators don't support SSL/TLS connections, either.

In this one, the programmer attempted to generate an integer in the range [0, 25] by multiplying Math.random() by 25 then rounding to the nearest integer. This creates a bias away from 0 and 25. To get a 0 or 25, the unrounded value must fall in the range [0, 0.5) or [24.5, 25] respectively. The width of these ranges is 0.5. For all other results (1 through 24), the width of the range is 1.0. For example, to get a 6, the unrounded value can fall anywhere in [5.5, 6.5).

 for (var i = 0; i < lengthOfPassword; i++) {  
      capitalise = Math.round(Math.random() * 1);  
      if (capitalise === 0) {  
           StrongPasswordArray[i] = theLetters.charAt(Math.round(Math.random() * 25)).toUpperCase();  
      }  
      else {  
           StrongPasswordArray[i] = theLetters.charAt(Math.round(Math.random() * 25));  
      }  
 }  

Lessons Learned:
  • Use a cryptographically secure random number generator when generating a key, password, or initialization vector.
  • Don't use floating point numbers for cryptography.
  • Test the output of your random number generator with tools like diehard.
  • Enforce an SSL/TLS connection if client-side scripts deal with sensitive information.

Thursday, July 11, 2013

osTicket: Fail Open, Fail Often

osTicket is (according to them) the "world's most popular open source customer support ticket system." They use one of the worst-designed encryption functions I've ever seen.

First of all, they're trying to use symmetric encryption to secure passwords. They should be using a slow hash (pbkdf2, bcrypt, scrypt) with salt. Edit: Actually, they have to be able to decrypt passwords, because they are used in POP3/IMAP connections. They have to be able to provide the plaintext passwords to a third party, not just verify them. Users' passwords are hashed.

Second, the encryption function fails open. If the mcrypt library is missing, encryption does nothing, and passwords are stored in the database in plain text. It does log a warning message, but who's going to see that?

I can almost feel the programmer's frustration, trying all possible combinations of trim(), IVs, and cipher modes to get the damn thing to decrypt properly. They tried to generate a random IV at encryption time, but did not realize they need to encode it with the ciphertext so it can be given to mcrypt_decrypt(). Instead, they use ECB mode, which ignores the IV. They even generate a random IV in the decryption function!

 function encrypt($text, $salt) {  
   
   //if mcrypt extension is not installed--simply return unencryted text and log a warning.  
   if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt')) {  
     $msg='Cryptography extension mcrypt is not enabled or installed. IMAP/POP passwords are being stored as plain text in database.';  
     Sys::log(LOG_WARN,'mcrypt missing',$msg);  
     return $text;  
   }  
   
   return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$salt, $text, MCRYPT_MODE_ECB,  
             mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));  
 }  
   
 function decrypt($text, $salt) {  
   if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt'))  
     return $text;  
   
   return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, base64_decode($text), MCRYPT_MODE_ECB,  
           mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));  
 }  

The "salt" (which is really the encryption key) is "randomly" generated at install time and stored in a source code file. If that happens to be missing, they use the md5 hash of the admin's email address.

 # This is to support old installations. with no secret salt.  
 if(!defined('SECRET_SALT')) define('SECRET_SALT',md5(TABLE_PREFIX.ADMIN_EMAIL));  

Here's how they generate the "salt." I shouldn't need to explain why this is wrong.

 function randCode($len=8) {  
   return substr(strtoupper(base_convert(microtime(),10,16)),0,$len);  
 }  
 $configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile);

Lessons Learned:
  • Fail safe. If your encryption library isn't available, fail hard, don't just return the plaintext.
  • Use a cryptographically secure random number generator to generate secrets.
  • Protect passwords by hashing them.

Silent Circle: Downplaying Critical Vulnerabilities

Silent Circle, an encrypted communications mobile app, censored vulnerability reports and did not disclose the existence of severe security bugs to their users. This is a very bad practice because users are not aware that they are vulnerable, and cannot decide to stop using Silent Circle until the vulnerabilities are fixed.


Lessons Learned:
  • Always inform your users of critical security bugs as soon as possible. Even if they can't fix it themselves, they can always chose not to use the software until it has been updated.

Cryptocat: RNG Bias, Decimal vs. Bytes

Cryptocat is a browser-based encrypted chat program. Cryptocat generates random numbers by generating random decimal digits, then converting them to a floating point number.

The way they were generating random decimal digits is biased. They did so by generating a random byte, discarding it if it was greater than 250, and otherwise dividing it by 10 to get an integer remainder between 0 and 9. They did this because 256 different values do not divide evenly into groups of 10, so there would be a bias. They didn't realize that between 0 and 250, there are actually 251 different integers, which still does not divide evenly into groups of 10.

  @@ -60,7 +60,7 @@ else {  
    var x, o = '';  
    while (o.length < 16) {  
     x = state.getBytes(1);  
 -   if (x[0] <= 250) {  
 +   if (x[0] < 250) {  
      o += x[0] % 10;  
     }  
    }   

They also confused bytes for decimal digits, and used too little entropy to generate their ECC keys.

 @@ -88,8 +88,8 @@ function checkSize(publicKey) {  
  // Generate private key (32 byte random number)  
  // Represented in decimal  
  multiParty.genPrivateKey = function() {  
 - rand = Cryptocat.randomString(32, 0, 0, 1);  
 - myPrivateKey = BigInt.str2bigInt(rand, 10);  
 + var rand = Cryptocat.randomString(64, 0, 0, 0, 1);  
 + myPrivateKey = BigInt.str2bigInt(rand, 16);  
   return myPrivateKey;  
  }  

NakedSecurity has an excellent blog post on the RNG flaw.

Lessons Learned
  • Test the output of your random number generators with tools like diehard.
  • Visually inspect the output of the functions you're using, to make sure it's in the format you expect.

SaltStack: RSA e = d = 1

An instance of textbook RSA has three parameters: e, d, and n, where e = d-1 (mod phi(n)). Encryption of a message m is me, and decryption of a ciphertext c is cd = (me)d = med = m1 = m.

When you set e = 1, encrypting a message does not change it, since raising any number to the power of 1 does not change it. SaltStack was using 1 as their RSA public key (e), so encryption was doing nothing:

 @@ -47,7 +47,7 @@ def gen_keys(keydir, keyname, keysize, user=None):  
    priv = '{0}.pem'.format(base)  
    pub = '{0}.pub'.format(base)  
 -  gen = RSA.gen_key(keysize, 1, callback=lambda x, y, z: None)  
 +  gen = RSA.gen_key(keysize, 65537, callback=lambda x, y, z: None)  
    cumask = os.umask(191)  
    gen.save_key(priv, None)  
    os.umask(cumask)   

What really amazes me is that the library they're using, M2Crypto, lets you do this without any kind of error or warning.

Lessons Learned:
  • Have a professional cryptographer review your parameter choices.
  • Write unit tests to check that messages can't be decrypted with a different key (edit: This might not catch the problem, see the comments).
  • Before using a cryptography library, it's a necessary to understand something about what it's doing behind the scenes.

Wednesday, July 10, 2013

Synergy: Integer Overflow, Key Reuse, IV Reuse

Synergy is a software that lets you share your mouse and keyboard between multiple computers. Until recently, it didn't have any support for encryption, which left users' keystrokes and mouse movements vulnerable to sniffing on the local network. Users worked around this limitation by tunneling Synergy's communications through a VPN or SSH. Lots of users complained, and the Synergy team decided to encrypt the connections. Instead of using a well-known protocol like TLS, they invented their own.

The most obvious problem is that they are re-using the same key and IV for encrypted communications in both directions. This is absolutely fatal for modes like CTR, OFB, GCM, and probably CFB, which are the ones Synergy supports. Update: A tool that exploits this vulnerability is available here.

Second, they derive the IV from the password, and because of an integer overflow bug, when the length of the password is congruent to its double mod 256, the key and the IV are the same. If the IV were the same size as the output of the hash function they're using (SHA256), you'd be able to recover the key for all passwords longer than 128 characters.

 if (!options.m_pass.empty()) {  
     createKey(m_key, options.m_pass, kKeyLength, static_cast<UInt8>(options.m_pass.length()));  
     byte iv[CRYPTO_IV_SIZE];  
     createKey(iv, options.m_pass, CRYPTO_IV_SIZE, static_cast<UInt8>(options.m_pass.length()) * 2);  
     setEncryptIv(iv);  
     setDecryptIv(iv);  
  }  

 void  
 CCryptoStream::createKey(byte* out, const CString& password, UInt8 keyLength, UInt8 hashCount)  
 {  
      assert(keyLength <= SHA256::DIGESTSIZE);  
      byte temp[SHA256::DIGESTSIZE];  
      byte* in = reinterpret_cast<byte*>(const_cast<char*>(password.c_str()));  
      SHA256().CalculateDigest(temp, in, password.length());  
      byte* tempKey = new byte[SHA256::DIGESTSIZE];  
      for (int i = 0; i < hashCount; ++i) {  
           memcpy(tempKey, temp, SHA256::DIGESTSIZE);  
           SHA256().CalculateDigest(temp, tempKey, SHA256::DIGESTSIZE);  
      }  
      delete[] tempKey;  
      memcpy(out, temp, keyLength);  
 }  

There is no attempt to detect or prevent replay attacks, and except for GCM mode, there is no authentication or data integrity guarantees (which you really want in something that's accepting keyboard commands).

The developers seem to be aware of how bad their crypto is, but for some reason refuse to fix or disable it.

Lessions learned:
  • Never invent your own network encryption protocols. Even professional cryptographers can't get it right the first time (SSL 3.0, TLS 1.0). Always use a good TLS library.
  • Always generate a random IV at the time of encryption.
  • Don't use a key for anything other than a key. All other uses will leak information about it.
  • It's better to have no encryption than to have broken encryption with a false sense of security.

CodeIgniter: Encryption is Not Authentication

After encrypting a string, the CodeIgniter PHP framework applies this function to the ciphertext. The function is a shift cipher using a hash of the encryption key.

The comment says they are doing it "to protect against Man-in-the-middle attacks on CBC mode ciphers." I have no idea what that means. I can only guess that they're slightly aware of the fact that encryption is not authentication, and that attackers can modify a CBC mode ciphertext to decrypt the way they want.

 /**  
  * Adds permuted noise to the IV + encrypted data to protect  
  * against Man-in-the-middle attacks on CBC mode ciphers  
  * http://www.ciphersbyritter.com/GLOSSARY.HTM#IV  
  *  
  * @param     string  
  * @param     string  
  * @return     string  
  */  
 protected function _add_cipher_noise($data, $key)  
 {  
      $key = $this->hash($key);  
      $str = '';  
   
      for ($i = 0, $j = 0, $ld = strlen($data), $lk = strlen($key); $i < $ld; ++$i, ++$j)  
      {  
           if ($j >= $lk)  
           {  
                $j = 0;  
           }  
   
           $str .= chr((ord($data[$i]) + ord($key[$j])) % 256);  
      }  
     
   return $str;  
 }  

Lessons Learned:
  • If you need to detect malicious changes to ciphertexts, use HMAC over the IV and ciphertext, or an authenticating mode like OCB.
  • If you find a vulnerability, don't come up with your own way to solve it. Find out how cryptographers have already solved it, and do it the right way.

CodeIgniter: Another rand() Stream Cipher

When the mcrypt functions are missing, the CodeIgniter PHP framework falls back to a homebrew stream cipher it calls "xor_encode."

 protected function _xor_encode($string, $key)  
 {  
      $rand = '';  
      do  
      {  
           $rand .= mt_rand();  
      }  
      while (strlen($rand) < 32);  
   
      $rand = $this->hash($rand);  
   
      $enc = '';  
      for ($i = 0, $ls = strlen($string), $lr = strlen($rand); $i < $ls; $i++)  
      {  
           $enc .= $rand[($i % $lr)].($rand[($i % $lr)] ^ $string[$i]);  
      }  
   
      return $this->_xor_merge($enc, $key);  
 }  
   
 protected function _xor_merge($string, $key)  
 {  
      $hash = $this->hash($key);  
      $str = '';  
      for ($i = 0, $ls = strlen($string), $lh = strlen($hash); $i < $ls; $i++)  
      {  
           $str .= $string[$i] ^ $hash[($i % $lh)];  
      }  
   
      return $str;  
 }  

Lessons Learned:
  • Don't invent your own cipher.
  • If your cryptography library is missing, don't encrypt! If you really need to fall back, include a well-tested implementation of a good cipher.

Myself: Using the Same Key to Encrypt Everything

This is one of my own mistakes. I saw that a lot of PHP encryption code is deeply flawed, so I wrote my own PHP encryption class and released it to the world. Little did I know, it was even more flawed than CakePHP's rand() cipher.

The problem was in the way I split the key given by the user into two keys: one for encryption, and the other for authentication. I did this by HMACing a purpose string (either "encryption" or "authentication") with the key. That works, but I screwed up the implementation. I didn't include the user's key in the call to hash_hmac(), and because PHP doesn't do type checking, it blindly accepted the next parameter, boolean "true", as the key.

Because of this mistake, everything was encrypted with the same key. Really bad.

Here's the diff of the fix:

  --- /tmp/Crypto.php   2013-05-02 06:39:28.059745329 -0600  
 +++ source/Crypto.php  2013-05-02 07:05:16.959752519 -0600  
 @@ -116,7 +116,7 @@  
    */  
    public static function CreateSubkey($master, $purpose, $bytes)  
    {  
 -    $source = hash_hmac("sha512", $purpose, true);  
 +    $source = hash_hmac("sha512", $purpose, $master, true);  
      if(strlen($source) < $bytes) {  
        trigger_error("Subkey too big.", E_USER_ERROR);  
        return $source; // fail safe  
 @@ -168,6 +168,16 @@  
        return false;  
      }  
 +    $key = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);  
 +    $data = "abcdef";  
 +    $ciphertext = Crypto::Encrypt($data, $key);  
 +    $wrong_key = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);  
 +    if (Crypto::Decrypt($ciphertext, $wrong_key))  
 +    {  
 +      echo "FAIL: Ciphertext decrypts with an incorrect key.";  
 +      return false;  
 +    }  
 +  
      echo "PASS\n";  
      return true;  
    }   

Lessons learned:
  • Always write test suites for your encryption code. Always test that you can't decrypt with the wrong key.

CakePHP: Using the IV as the Key

A previous post highlighted CakePHP's abysmal homebrew cipher. They realized it was weak and attempted to fix it by using the Rijdnael cipher instead. They even used CBC mode. But instead of generating a random IV for every encryption, they used the key as the IV. This is really bad, because you can easily recover the key with a chosen ciphertext or chosen plaintext attack.

They realized that was a bad idea, and tried to switch to randomly-generated IVs in a backwards compatible way. Unfortunately, the way they did it makes 1 in every  216 of the old ciphertexts undecryptable.

They are also using zero-byte padding, just stripping the trailing zero bytes after decryption. This is ambiguous: If the original message ends in zero bytes, they will be stripped off after the decryption, and that part of the message will be lost.

 public static function rijndael($text, $key, $operation) {  
      if (empty($key)) {  
           trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::rijndael()'), E_USER_WARNING);  
           return '';  
      }  
      if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {  
           trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);  
           return '';  
      }  
      if (strlen($key) < 32) {  
           trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);  
           return '';  
      }  
      $algorithm = MCRYPT_RIJNDAEL_256;  
      $mode = MCRYPT_MODE_CBC;  
      $ivSize = mcrypt_get_iv_size($algorithm, $mode);  
      $cryptKey = substr($key, 0, 32);  
      if ($operation === 'encrypt') {  
           $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);  
           return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);  
      }  
      // Backwards compatible decrypt with fixed iv  
      if (substr($text, $ivSize, 2) !== '$$') {  
           $iv = substr($key, strlen($key) - 32, 32);  
           return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");  
      }  
      $iv = substr($text, 0, $ivSize);  
      $text = substr($text, $ivSize + 2);  
      return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");  
 }  

Lessons learned:
  • Always use unambiguous encodings, i.e. don't rely on low probability of random data containing your separator, and use unambiguous padding.
  • Don't use the key as anything other than the key. If you do, you're almost certainly leaking information about it.
  • Always use an IV that is randomly generated (from a secure CSPRNG) at the time of encryption.

CakePHP: Using rand() to Build a Stream Cipher

The following function can be found in the CakePHP framework. It's marked as deprecated, and it's going to be removed in a future version, but it's pretty bad:

  public static function cipher($text, $key) {  
      if (empty($key)) {  
           trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::cipher()'), E_USER_WARNING);  
           return '';  
      }  
      srand(Configure::read('Security.cipherSeed'));  
      $out = '';  
      $keyLength = strlen($key);  
      for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {  
           $j = ord(substr($key, $i % $keyLength, 1));  
           while ($j--) {  
                rand(0, 255);  
           }  
           $mask = rand(0, 255);  
           $out .= chr(ord(substr($text, $i, 1)) ^ $mask);  
      }  
      srand();  
      return $out;  
 }  

Lesson learned:
  • Don't invent your own cipher. Use a well-known cipher that has been tested and analyzed extensively by experts, like AES, Serpent, or Twofish.