Things I learned while implementing version 1.0 of pws
After releasing version 0.9 of my cli password manager, I received friendly feedback and suggestions, which encouraged me to further improve it. Here are some of my experiences implementing pws 1.0:
Key Derivation Function
The passwords reside in an encrypted file, which is fine. However, this won’t be of any use, if an attacker just bruteforces your password. You cannot protect the file from being bruteforced, but you can hamper the attacker by making it hard to transform the password into the encryption key (= taking longer). This is achieved by using a hard-to-calculate KDF. Available Ruby implementations of such a KDF include pbkdf2, bcrypt and scrypt.
I went with pbkdf2, which is part of Ruby’s standard library (OpenSSL).
Ruby Marshalling
Up until now, I used a simple Marshal.dump
to convert the password entry Ruby hashes into a string. I still like the simplicity of this approach. However, someone pointed out that the marshalling format could change in a future Ruby version. While I cannot see that’s the case in near future, I nevertheless think it’s a good argument and changed the marshalling to a custom format.
Pack and Unpack
I got familiar with the low level String#unpack
and Array#pack
methods. I already used both methods pretty often to convert between various formats, but I didn’t get the general concept, when to use which of both methods. However, it’s pretty easy:
You have a custom binary format, which is stored in a file or received from some stream. It’s always a sequence of bytes, so it’s somehow a string. Your information is packed into the string, defined by a template. So you can unpack the String, using a template definition. The result is an array of all the data elements that you’ve defined in the template. You can pack the data in that array to get a byte sequence (stored in a Ruby string) and save it or send it away.
Pretty logic, isn’t it?
Aruba Timeout Issues
Using the cucumber command-line testing framework has its pros and cons. One of the drawbacks are the timeout issues that let test cases fail for no real reason. The impact is the worst when using an “output from” step.. At some point, running the test suite locally was no problem, anymore, but I had a hard time getting the test suite green on travis-ci.
Encoding Confusion
It seems that Ruby’s string comparison does not only compare bytes, but also the encoding:
>> a = '⌨' #=> "⌨"
>> a.encoding #=> #<Encoding:UTF-8>
>> b = a.dup #=> "⌨"
>> b.force_encoding('ascii') #=> "\xE2\x8C\xA8"
>> b.encoding #=> #<Encoding:US-ASCII>
>> a == b #=> false
However, it will work, if not using unicode characters:
>> a = "no-unicode" #=> "no-unicode"
>> a.encoding #=> #<Encoding:UTF-8>
>> b = a.dup #=> "no-unicode"
>> b.force_encoding('ascii') #=> "no-unicode"
>> b.encoding #=> #<Encoding:US-ASCII>
>> a == b #=> true
Is this behavior documented somewhere? Which rules does it follow?
JRuby vs. OpenSSL
JRuby’s OpenSSL implementation got some problems. Firstly, you need the jruby-openssl gem – but when loading it into MRI, you get errors, so you cannot directly specify the gem dependency. You can specify a platform in Gemfiles, but that’s bundler specific and does not help for deploying the gem on rubygems.org. Is this hack the only way to use platform specific gems!?
Another point: The JRuby OpenSSL implementation lacks the PCKS5 part that contains the pbkdf2 function. A workaround for this problem is to use this well-designed pbkdf2 Ruby implementation, which is a little bit slower than OpenSSL’s.
These and some more minor problems lead me to postpone JRuby support.
PWS 1.0
These were the Ruby problems I’ve had to fight with. Now I am pretty happy with the current implementation of pws. Thanks for reading.
Anonymous | May 31, 2012
About the unicode and ascii stuff, they just use the same values. That's why. If the strings are pure ASCII chars, they are same regardless of encoding.