lib/bicrypt.rb in bicrypt-1.0.0 vs lib/bicrypt.rb in bicrypt-1.1.0
- old
+ new
@@ -1,180 +1,94 @@
-# = BiCrypt
+# BiCrypt - A Simple Two-Way Encryption Class
#
-# A simple two-way encryption class.
-#
-# == Authors
-#
-# * Trans
-#
-# == Copying
-#
# Copyright (c) 2007 Trans
#
-# Ruby License
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
#
-# This module is free software. You may use, modify, and/or redistribute this
-# software under the same terms as Ruby.
+# http://www.apache.org/licenses/LICENSE-2.0
#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
require 'stringio'
# = 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()
+#
class BiCrypt
ULONG = 0x100000000
- def block_size
- return(8)
- end
+ # These are the S-boxes given in Applied Cryptography 2nd Ed., p. 333
+ SBOX = [
+ [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]
+ ]
+ # These are the S-boxes given in the GOST source code listing in Applied
+ # Cryptography 2nd Ed., p. 644. They appear to be from the DES S-boxes
+ # [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 ],
+ # [ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 ],
+ # [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 ],
+ # [ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 ],
+ # [ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 ],
+ # [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 ],
+ # [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 ],
+ # [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 ]
+
+ #
def initialize(userKey)
+ @sBox = SBOX
- # These are the S-boxes given in Applied Cryptography 2nd Ed., p. 333
- @sBox = [
- [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]
- ]
+ if userKey.size < 8
+ userKey += '01234567'
+ end
- # These are the S-boxes given in the GOST source code listing in Applied
- # Cryptography 2nd Ed., p. 644. They appear to be from the DES S-boxes
- # [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 ],
- # [ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 ],
- # [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 ],
- # [ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 ],
- # [ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 ],
- # [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 ],
- # [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 ],
- # [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 ]
+ #@sTable = precalculate_s_table()
- # precalculate the S table
- @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
- def precalculate_S_table()
- 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)
- }
- }
- return(sTable)
- end
-
-
- 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
-
- 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
-
-
- 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
-
- def encrypt_block(block)
- xl, xr = block.unpack('NN')
- xl, xr = encrypt_pair(xl, xr)
- encrypted = [xl, xr].pack('NN')
- return(encrypted)
- end
-
-
- def decrypt_block(block)
- xl, xr = block.unpack('NN')
- xl, xr = decrypt_pair(xl, xr)
- decrypted = [xl, xr].pack('NN')
- return(decrypted)
- end
-
- # When this module is mixed in with an encryption class, the class
- # must provide three methods: encrypt_block(block) and decrypt_block(block)
- # and block_size()
-
- def generate_initialization_vector(words)
- srand(Time.now.to_i)
- vector = ""
- words.times {
- vector << [rand(ULONG)].pack('N')
- }
- return(vector)
- end
-
-
+ # Encrypt an IO stream.
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 = block ^ chain
+ block = xor(block, chain)
encrypted = encrypt_block(block)
cryptStream.write(encrypted)
chain = encrypted
end
@@ -187,106 +101,210 @@
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 = block ^ chain
+ block = xor(block, chain)
encrypted = encrypt_block(block)
cryptStream.write(encrypted)
end
-
+ # Decrypt an encrypted IO stream.
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 = decrypted ^ chain
+ 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
-
- 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
-
-
+ # Encrypt a file.
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
-
+ # Decrypt an encrypted file.
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
-
+ # Encrypt a string.
def encrypt_string(plainText)
plainStream = StringIO.new(plainText)
cryptStream = StringIO.new('')
encrypt_stream(plainStream, cryptStream)
cryptText = cryptStream.string
return(cryptText)
end
-
+ # Decrypt an encrypted string.
def decrypt_string(cryptText)
cryptStream = StringIO.new(cryptText)
plainStream = StringIO.new('')
decrypt_stream(cryptStream, plainStream)
plainText = plainStream.string
return(plainText)
end
-end
+ private
+ # S-boxes.
+ attr :sBox
-class String
+ # Calculated S-boxes
+ 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
+ #
+ def encrypt_block(block)
+ xl, xr = block.unpack('NN')
+ xl, xr = encrypt_pair(xl, xr)
+ encrypted = [xl, xr].pack('NN')
+ return(encrypted)
+ end
+
+ #
+ def decrypt_block(block)
+ xl, xr = block.unpack('NN')
+ xl, xr = decrypt_pair(xl, xr)
+ decrypted = [xl, xr].pack('NN')
+ return(decrypted)
+ end
+
+ #
+ def block_size
+ 8
+ end
+
+ #
+ 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
+
+ #
+ 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
+
+ #
+ 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
+
+ #
+ def generate_initialization_vector(words)
+ srand(Time.now.to_i)
+ vector = ""
+ words.times {
+ vector << [rand(ULONG)].pack('N')
+ }
+ return(vector)
+ end
+
+ #
+ 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
+
# 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"
#
- def ^(aString)
- a = self.unpack('C'*(self.length))
- b = aString.unpack('C'*(aString.length))
+ #--
+ # NOTE: This used to be a String#^ extension in v1.0. So if
+ # an uncaught bug should arise check this first.
+ #++
+ 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) { |pos|
+ 0.upto(a.length - 1) do |pos|
x = a[pos] ^ b[pos]
xor << x.chr()
- }
+ end
return(xor)
end
end