Should you trust lastpass.com?
|
The article you are reading has moved! It is now available at: http://blog.tinisles.com/2010/01/should-you-trust-lastpass-com/ |
lastpass.com looks like a handy online service for storing all your website passwords. Browser extensions are available to make the whole process easier. If you use the web from several PCs it does sound nice to have all your passwords available and synced between your PCs.
First thought was wether I should hand over my passwords to the 3rd party, hopefully they are storing them with some serious crypto. Perusing their FAQ I found some info on this:
We only support keeping the encryption done on your computer so LastPass can't see your sensitive data
...your sensitive data is always encrypted and decrypted locally on your computer before being synchronized. Your master password never leaves your computer and your key never leaves your computer. No one at LastPass (or anywhere else) can decrypt your data without you giving up your password (we will never ask you for it). Your key is created by taking a SHA-256 hash of your password. When you login, we make a hash of your username concatenated with your password, and that hash is what's sent to verify if you can download your encrypted data.
So they are encrypting everything with a key derived from your password - plus they don't know your password - so they CANNOT access any of your passwords. Nice. But why stop there? Let's fire up fiddler to make sure they definitely don't have my passwords.
I've created a junk account on lastpass username: russell.sayers.junk@gmail.com, password: test1234 (the account will be gone by the time you read this!). The first form I see submitted to the server is when I create my account:
username | russell.sayers.junk@gmail.com |
hash | 53c81a859a3f3d4dc3762d3a47bab07fad7ad3f2673724deb20fb420e8bdc03a |
password | ******** |
password2 | ******** |
password_hint | testing |
timezone2 | +10:00,1 |
language2 | en-US |
agree | on |
agreeupload | on |
loglogins | on |
improve | on |
json | 1 |
I don't see my password going to their servers in the clear. I can confirm that the hash getting sent is geniune by creating the same hash elsewhere via an online SHA256 generator, or writing some code myself. Try it yourself, the hash created is SHA256(SHA256(username + password) + password) - everything checks out. All the C# code to verify the encryption/hashing is at the end of the article.
The login form:
method | web |
hash | 53c81a859a3f3d4dc3762d3a47bab07fad7ad3f2673724deb20fb420e8bdc03a |
username | russell.sayers.junk@gmail.com |
encrypted_username | T2tBleI3PxuLOoNEwNkv5PZ/rYr5dDIoYZS+We4vER4= |
otp | |
gridresponse | |
trustlabel | |
uuid | |
sesameotp | |
russell.sayers.junk@gmail.com | |
password | |
Again the same hash is being used to verify me when I login. I can see an encrypted username is being sent (although I'm not sure why?), rooting around in the javascript I can see the key being used for encyption is SHA(username+password). Importantly this is different to the hash being used to authenticate me - as we don't want the server to be able to decypt anything encypted by the client, i.e. they would have to know my password to derive the same key on their side.
The form to add a new website:
hasplugin | 0 |
extjs | 1 |
search | |
purgeext | 0 |
deleteext | |
undeleteext | 0 |
ajax | 1 |
ob | |
basic_auth | 0 |
isbookmark | 0 |
openid_url | |
aid | 0 |
useurid | 0 |
fromwebsite | 1 |
name | pyJlY+AX0Aoczlx50hwlHg== |
grouping | |
origurl | |
url | 66616365626f6f6b2e636f6d |
username | ICkGIGAn7SIk16iKNkl3DA== |
password | dl78FYUSIdsxxSdkBuBWEA== |
extra | faESoIpzmCQg5PeHpXN0GQ== |
So when I log back into lastpass, I can drill down into this entry and see it again. Let's make sure everything looks okay here. The HTML that renders this screen is available in fiddler:
<tr> <td class='col1'>Name</td> <td><input name='name' id='name' type='text' value='pyJlY+AX0Aoczlx50hwlHg==' style='width: 250px'></td> </tr> ... <tr> <td class='col1'>Username</td> <td><input name='username' id='idusername' type='text' value='SgVkuVKH4MjkP+Saz64UhA=='> </td> </tr> ... <tr> <td class='col1'>Notes</td> <td><textarea name='extra' id='extra' rows='6' cols='35'>1rZ3sSuggtdavyCu446GZA==</textarea></td> </tr>The server is sending down our encrypted details, and relying on the client to decrypt everything.
So, yes - you CAN trust lastpass.com with your passwords! Don't just take my word for it; fire up Fiddler, and compare the hashed/encrypted values with what you expect.
Lastly the c# code to confirm the AES encrypted strings above are geniune.
static void Main(string[] args) { string username = "russell.sayers.junk@gmail.com"; string password = "test1234"; string hash_auth = ByteArrayToHexString(SHA256(ByteArrayToHexString(SHA256(username+password)) + password)); byte[] hash_key = SHA256(username + password); Console.WriteLine("hash for authentication => " + hash_auth); Console.WriteLine("hash for encryption => " + ByteArrayToHexString(hash_key)); Console.WriteLine("'{0}' encrypted => {1}", username, Encrypt(username, hash_key, "")); Console.WriteLine("'{0}' encrypted => {1}", "facebook", Encrypt("facebook", hash_key, "")); Console.WriteLine("'{0}' encrypted => {1}", "cryptolearner", Encrypt("cryptolearner", hash_key, "")); Console.WriteLine("'{0}' encrypted => {1}", "password", Encrypt("password", hash_key, "")); Console.WriteLine("'{0}' encrypted => {1}", "no notes", Encrypt("no notes", hash_key, "")); } static byte[] SHA256(string data) { byte[] indata = Encoding.UTF8.GetBytes(data); SHA256 shaM = new SHA256Managed(); return shaM.ComputeHash(indata); } /// <remarks>From http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/3928b8cb-3703-4672-8ccd-33718148d1e3</remarks> static string ByteArrayToHexString(byte[] data) { StringBuilder sb = new StringBuilder(data.Length * 2); foreach (byte b in data) { sb.AppendFormat("{0:x2}", b); } return sb.ToString(); } /// <remarks>From http://stackoverflow.com/questions/1079131/c-aes-256-encryption</remarks> static public string Encrypt(string plaintext, byte[] KeyBytes, string InitialVector) { byte[] PlainTextBytes = Encoding.UTF8.GetBytes(plaintext); byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector); RijndaelManaged SymmetricKey = new RijndaelManaged(); SymmetricKey.Mode = CipherMode.ECB; SymmetricKey.Padding = PaddingMode.PKCS7; ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes); MemoryStream MemStream = new MemoryStream(); CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write); CryptoStream.Write(PlainTextBytes, 0, PlainTextBytes.Length); CryptoStream.FlushFinalBlock(); byte[] CipherTextBytes = MemStream.ToArray(); MemStream.Close(); CryptoStream.Close(); return Convert.ToBase64String(CipherTextBytes); }
Labels: cryptography