class BiCrypt

BiCrypt

A simple two-way encryption class.

This class is based on algorithms given in Applied Cryptography 2nd Ed.

If subclassed, the new encryption class must provide three methods:

Constants

SBOX

These are the S-boxes given in Applied Cryptography 2nd Ed., p. 333

ULONG
VERSION

Attributes

sBox[R]

S-boxes.

Public Class Methods

new(userKey) click to toggle source
# File lib/bicrypt.rb, line 61
def initialize(userKey)
  @sBox = SBOX

  if userKey.size < 8
    userKey += '01234567'
  end

  #@sTable = precalculate_s_table()

  # derive the 32-byte key from the user-supplied key
  userKeyLength = userKey.length

  @key = userKey[0..31].unpack('C'*32)

  if (userKeyLength < 32)
    userKeyLength.upto(31) { @key << 0 }
  end
end

Public Instance Methods

decrypt_file(cryptFilename, plainFilename) click to toggle source

Decrypt an encrypted file.

# File lib/bicrypt.rb, line 141
def decrypt_file(cryptFilename, plainFilename)
  cryptFile = carefully_open_file(cryptFilename, 'rb')
  plainFile = carefully_open_file(plainFilename, 'wb+')
  decrypt_stream(cryptFile, plainFile)
  cryptFile.close unless cryptFile.closed?
  plainFile.close unless plainFile.closed?
end
decrypt_stream(cryptStream, plainStream) click to toggle source

Decrypt an encrypted IO stream.

# File lib/bicrypt.rb, line 114
def decrypt_stream(cryptStream, plainStream)
  # Cypher-block-chain mode
  chain = cryptStream.read(block_size())

  while (block = cryptStream.read(block_size()))
    decrypted = decrypt_block(block)
    plainText = xor(decrypted, chain)
    plainStream.write(plainText) unless cryptStream.eof?
    chain = block
  end

  # write the final block, omitting the padding
  buffer = plainText.split('')
  remainingMessageBytes = buffer.last.unpack('C').first
  remainingMessageBytes.times { plainStream.write(buffer.shift) }
end
decrypt_string(cryptText) click to toggle source

Decrypt an encrypted string.

# File lib/bicrypt.rb, line 159
def decrypt_string(cryptText)
  cryptStream = StringIO.new(cryptText)
  plainStream = StringIO.new('')
  decrypt_stream(cryptStream, plainStream)
  plainText = plainStream.string
  return(plainText)
end
encrypt_file(plainFilename, cryptFilename) click to toggle source

Encrypt a file.

# File lib/bicrypt.rb, line 132
def encrypt_file(plainFilename, cryptFilename)
  plainFile = carefully_open_file(plainFilename, 'rb')
  cryptFile = carefully_open_file(cryptFilename, 'wb+')
  encrypt_stream(plainFile, cryptFile)
  plainFile.close unless plainFile.closed?
  cryptFile.close unless cryptFile.closed?
end
encrypt_stream(plainStream, cryptStream) click to toggle source

Encrypt an IO stream.

# File lib/bicrypt.rb, line 83
def encrypt_stream(plainStream, cryptStream)
  # Cypher-block-chain mode

  initVector = generate_initialization_vector(block_size() / 4)
  chain = encrypt_block(initVector)
  cryptStream.write(chain)

  while ((block = plainStream.read(block_size())) && (block.length == block_size()))
    block = xor(block, chain)
    encrypted = encrypt_block(block)
    cryptStream.write(encrypted)
    chain = encrypted
  end

  # write the final block
  # At most block_size()-1 bytes can be part of the message.
  # That means the final byte can be used to store the number of meaningful
  # bytes in the final block
  block = '' if block.nil?
  buffer = block.split('')
  remainingMessageBytes = buffer.length
  # we use 7-bit characters to avoid possible strange behavior on the Mac
  remainingMessageBytes.upto(block_size()-2) { buffer << rand(128).chr }
  buffer << remainingMessageBytes.chr
  block = buffer.join('')
  block = xor(block, chain)
  encrypted = encrypt_block(block)
  cryptStream.write(encrypted)
end
encrypt_string(plainText) click to toggle source

Encrypt a string.

# File lib/bicrypt.rb, line 150
def encrypt_string(plainText)
  plainStream = StringIO.new(plainText)
  cryptStream = StringIO.new('')
  encrypt_stream(plainStream, cryptStream)
  cryptText = cryptStream.string
  return(cryptText)
end

Private Instance Methods

block_size() click to toggle source
# File lib/bicrypt.rb, line 205
def block_size
  8
end
carefully_open_file(filename, mode) click to toggle source
# File lib/bicrypt.rb, line 273
def carefully_open_file(filename, mode)
  begin
    aFile = File.new(filename, mode)
  rescue
    puts "Sorry. There was a problem opening the file <#{filename}>."
    aFile.close() unless aFile.nil?
    raise
  end
  return(aFile)
end
decrypt_block(block) click to toggle source
# File lib/bicrypt.rb, line 197
def decrypt_block(block)
  xl, xr = block.unpack('NN')
  xl, xr = decrypt_pair(xl, xr)
  decrypted = [xl, xr].pack('NN')
  return(decrypted)
end
decrypt_pair(xl, xr) click to toggle source
# File lib/bicrypt.rb, line 233
def decrypt_pair(xl, xr)
  xr ^= f(xl+@key[0])
  xl ^= f(xr+@key[1])
  xr ^= f(xl+@key[2])
  xl ^= f(xr+@key[3])
  xr ^= f(xl+@key[4])
  xl ^= f(xr+@key[5])
  xr ^= f(xl+@key[6])
  xl ^= f(xr+@key[7])
  3.times {
    xr ^= f(xl+@key[7])
    xl ^= f(xr+@key[6])
    xr ^= f(xl+@key[5])
    xl ^= f(xr+@key[4])
    xr ^= f(xl+@key[3])
    xl ^= f(xr+@key[2])
    xr ^= f(xl+@key[1])
    xl ^= f(xr+@key[0])
  }
  return([xr, xl])
end
encrypt_block(block) click to toggle source
# File lib/bicrypt.rb, line 189
def encrypt_block(block)
  xl, xr = block.unpack('NN')
  xl, xr = encrypt_pair(xl, xr)
  encrypted = [xl, xr].pack('NN')
  return(encrypted)
end
encrypt_pair(xl, xr) click to toggle source
# File lib/bicrypt.rb, line 210
def encrypt_pair(xl, xr)
  3.times {
    xr ^= f(xl+@key[0])
    xl ^= f(xr+@key[1])
    xr ^= f(xl+@key[2])
    xl ^= f(xr+@key[3])
    xr ^= f(xl+@key[4])
    xl ^= f(xr+@key[5])
    xr ^= f(xl+@key[6])
    xl ^= f(xr+@key[7])
  }
  xr ^= f(xl+@key[7])
  xl ^= f(xr+@key[6])
  xr ^= f(xl+@key[5])
  xl ^= f(xr+@key[4])
  xr ^= f(xl+@key[3])
  xl ^= f(xr+@key[2])
  xr ^= f(xl+@key[1])
  xl ^= f(xr+@key[0])
  return([xr, xl])
end
f(longWord) click to toggle source
# File lib/bicrypt.rb, line 256
def f(longWord)
  longWord = longWord % ULONG
  a, b, c, d = [longWord].pack('L').unpack('CCCC')
  return(sTable[3][d] ^ sTable[2][c] ^ sTable[1][b] ^ sTable[0][a])
end
generate_initialization_vector(words) click to toggle source
# File lib/bicrypt.rb, line 263
def generate_initialization_vector(words)
  srand(Time.now.to_i)
  vector = ""
  words.times {
    vector << [rand(ULONG)].pack('N')
  }
  return(vector)
end
sTable() click to toggle source

Calculated S-boxes

# File lib/bicrypt.rb, line 173
def sTable
  @sTable ||= (
    sTable = [[], [], [], []]
    0.upto(3) { |i|
      0.upto(255) { |j|
        t = sBox[2*i][j % 16] | (sBox[2*i+1][j/16] << 4)
        u = (8*i + 11) % 32
        v = (t << u) | (t >> (32-u))
        sTable[i][j] = (v % ULONG)
      }
    }
    sTable
  )
end
xor(str1, str2) click to toggle source

Binary XOR of two strings.

puts "\000\000\001\001" ^ "\000\001\000\001"
puts  "\003\003\003" ^ "\000\001\002"

produces

"\000\001\001\000"
"\003\002\001"
# File lib/bicrypt.rb, line 298
def xor(str1, str2)
  a = str1.unpack('C'*(str1.length)) #.bytes.to_a
  b = str2.unpack('C'*(str2.length)) #.bytes.to_a
  if (b.length < a.length)
    (a.length - b.length).times { b << 0 }
  end
  xor = ""
  0.upto(a.length - 1) do |pos|
    x = a[pos] ^ b[pos]
    xor << x.chr()
  end
  return(xor)
end