martin carpenter

twofish.rb

2010/03/11

tags: twofish ruby gem

api documentation http://mcarpenter.org/rdoc/twofish.rb/index.html
github home http://github.com/mcarpenter/twofish.rb
repository URLs https://github.com/mcarpenter/twofish.rb.git
git://github.com/mcarpenter/twofish.rb.git

For a small Ruby project I needed Twofish encryption but couldn't find a suitable library. I tried calling Bouncycastle from JRuby (example in the README below). The results were perfectly satisfactory but the process start up time was not, so I translated Guido Flohr's Pure Perl CPAN module to Ruby.

For my small project (reading PasswordSafe vaults which are generally pretty small) execution speed was not an issue. YMMV. The latest version of twofish.rb is compatible with Ruby 1.8 and 1.9.

This gem implements the Twofish symmetric encryption algorithm in pure Ruby. The original paper describing the cipher “Twofish: A 128-Bit Block Cipher” (Schneier, Kelsey, Whiting, Wagner, Hall, Ferguson) and further information on Twofish can be found at www.schneier.com/twofish.html.

This implementation is derived with kind permission from Guido Flohr’s “pure Perl” module Crypt-Twofish_PP: search.cpan.org/~guido/Crypt-Twofish_PP-0.17. The overall structure and a good number of the comments from that implementation have been retained.

example

ECB mode:

require 'twofish'

key = '1234567890123456'
tf = Twofish.new(key, :padding => :zero_byte)
ciphertext = tf.encrypt('Lorem ipsum dolor sit amet')

CBC mode with manually specified initialization vector (may alternatively be specified in constructor options hash):

require 'twofish'

key = '1234567890123456'
tf = Twofish.new(key, :mode => :cbc, :padding => :zero_byte)
tf.iv = 'abcdefghijklmnop'
ciphertext = tf.encrypt('Lorem ipsum dolor sit amet')

unit tests

test_twofish.rb defines the unit tests. The iterative test vectors from the original paper are used (although only the final result is checked). The CBC mode test vectors were checked using the BouncyCastle implementation with JRuby as follows:

include Java

require 'bcprov-jdk16-145.jar'

include_class Java::org.bouncycastle.jce.provider.BouncyCastleProvider
include_class Java::org.bouncycastle.crypto.modes.CBCBlockCipher
include_class Java::org.bouncycastle.crypto.engines.TwofishEngine
include_class Java::org.bouncycastle.crypto.params.KeyParameter
include_class Java::org.bouncycastle.crypto.params.ParametersWithIV

plaintext = (""*32).to_java_bytes
ciphertext = (""*32).to_java_bytes
key = (""*16).to_java_bytes
iv = (""*16).to_java_bytes
key_parameter = KeyParameter.new(key)
cipher_parameter = ParametersWithIV.new(key_parameter, iv)
cipher = CBCBlockCipher.new TwofishEngine.new
cipher.init(true, cipher_parameter) # true == encrypt

len = 0
while len < plaintext.length
  len += cipher.processBlock(plaintext, len, ciphertext, len)
end

puts ciphertext.to_a.pack('c*').unpack('H*')

The original block test vectors are available as a machine-readable file from www.schneier.com/code/ecb_ival.txt

To run the unit tests:

bob@devbox:~/twofish$ rake test

documentation

Aside from this README, rdoc documentation may be built as follows:

bob@devbox:~/twofish$ rake rdoc

The documentation will be built in the rdoc subdirectory.

benchmark

A simple benchmark can be found in test/benchmark.rb and can be run thusly:

bob@devbox:~/twofish$ rake benchmark

Ruby 1.9 is approximately three times faster than ruby 1.8.7 (Ubuntu x86-64).

bugs, omissions and weaknesses

Encryption and decryption are (not unexpectedly) slow. If you need a faster implementation for Ruby then the BouncyCastle provider (www.bouncycastle.org) plays well with JRuby (www.jruby.org). See above (“Unit tests”) for a sample script.

Ruby >=1.9 introduces string encodings. The current workaround uses #ord and #chr but this is not very satisfactory: it would be preferable to move to byte arrays throughout.

The only padding mechanisms implemented are “none” (don’t pad) and “zero byte” (append zero bytes to make up a full block). Zero byte padding has a well-known failure mode: if the plaintext terminates in null bytes then these may be erroneously removed when un-padding is performed.

If no initialization vector is provided for CBC mode then the system random number generator (Kernel#rand) is used to generate one. The system random number generator may be weaker than desired.

The IV is not silently prepended to the ciphertext since if the resulting ciphertext is transmitted as is this can introduce a weakness. IV should ideally be transmitted OOB. There is a good explanation of this risk at Terry Ritter’s page www.ciphersbyritter.com/GLOSSARY.HTM#CipherBlockChaining

if the IV is not enciphered, and if the opponents
can intercept and change the IV in transit, they can
change the first-block plaintext bit-for-bit, without
a block-wide garble. That means an opponent could make
systematic changes to the first block plaintext simply
by changing the IV.