Class: BiCrypt

Inherits:
Object
  • Object
show all
Defined in:
lib/bicrypt.rb

Overview

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:

  • encrypt_block(block)

  • decrypt_block(block)

  • block_size()

Constant Summary collapse

VERSION =
'1.2.0'
ULONG =
0x100000000
SBOX =

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

[
  [4, 10, 9, 2, 13, 8, 0, 14, 6, 11, 1, 12, 7, 15, 5, 3],
  [14, 11, 4, 12, 6, 13, 15, 10, 2, 3, 8, 1, 0, 7, 5, 9],
  [5, 8, 1, 13, 10, 3, 4, 2, 14, 15, 12, 7, 6, 0, 9, 11],
  [7, 13, 10, 1, 0, 8, 9, 15, 14, 4, 6, 12, 11, 2, 5, 3],
  [6, 12, 7, 1, 5, 15, 13, 8, 4, 10, 9, 14, 0, 3, 11, 2],
  [4, 11, 10, 0, 7, 2, 1, 13, 3, 6, 8, 5, 9, 12, 15, 14],
  [13, 11, 4, 1, 3, 15, 5, 9, 0, 10, 14, 7, 6, 8, 2, 12],
  [1, 15, 13, 0, 5, 7, 10, 4, 9, 2, 3, 14, 6, 11, 8, 12]
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(userKey) ⇒ BiCrypt

Returns a new instance of BiCrypt.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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

Instance Attribute Details

#sBoxObject (readonly, private)

S-boxes.



170
171
172
# File 'lib/bicrypt.rb', line 170

def sBox
  @sBox
end

Instance Method Details

#block_sizeObject (private)



205
206
207
# File 'lib/bicrypt.rb', line 205

def block_size
  8
end

#carefully_open_file(filename, mode) ⇒ Object (private)



273
274
275
276
277
278
279
280
281
282
# 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) ⇒ Object (private)



197
198
199
200
201
202
# 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_file(cryptFilename, plainFilename) ⇒ Object

Decrypt an encrypted file.



141
142
143
144
145
146
147
# 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_pair(xl, xr) ⇒ Object (private)



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# 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

#decrypt_stream(cryptStream, plainStream) ⇒ Object

Decrypt an encrypted IO stream.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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) ⇒ Object

Decrypt an encrypted string.



159
160
161
162
163
164
165
# 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_block(block) ⇒ Object (private)



189
190
191
192
193
194
# 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_file(plainFilename, cryptFilename) ⇒ Object

Encrypt a file.



132
133
134
135
136
137
138
# 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_pair(xl, xr) ⇒ Object (private)



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 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

#encrypt_stream(plainStream, cryptStream) ⇒ Object

Encrypt an IO stream.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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) ⇒ Object

Encrypt a string.



150
151
152
153
154
155
156
# 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

#f(longWord) ⇒ Object (private)



256
257
258
259
260
# 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) ⇒ Object (private)



263
264
265
266
267
268
269
270
# 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

#sTableObject (private)

Calculated S-boxes



173
174
175
176
177
178
179
180
181
182
183
184
185
186
# 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) ⇒ Object (private)

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"

– NOTE: This used to be a String#^ extension in v1.0. So if an uncaught bug should arise check this first. ++



298
299
300
301
302
303
304
305
306
307
308
309
310
# 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