Module: AesCtr

Defined in:
lib/aesctr-ruby.rb

Overview

AES

Class Method Summary collapse

Class Method Details

.decrypt(ciphertext, password, nBits) ⇒ Object

Decrypt a text encrypted by AES in counter mode of operation

Parameters:

  • string

    ciphertext Source text to be encrypted

  • string

    password The password to use to generate a key

  • int

    nBits Number of bits to be used in the key (128, 192, or 256)



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/aesctr-ruby.rb', line 224

def self.decrypt(ciphertext, password, nBits)
  blockSize = 16  # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  return '' unless(nBits==128 || nBits==192 || nBits==256)
  ciphertext = Base64.decode64(ciphertext);

  nBytes = nBits/8  # no bytes in key (16/24/32)
  pwBytes = []
  0.upto(nBytes-1){|i| pwBytes[i] = password.bytes.to_a[i] & 0xff || 0}
  key = Aes::cipher(pwBytes, Aes::keyExpansion(pwBytes)) # gives us 16-byte key
  key = key.concat(key.slice(0, nBytes-16)) # expand key to 16/24/32 bytes long
  # recover nonce from 1st 8 bytes of ciphertext
  counterBlock = []
  ctrTxt = ciphertext[0,8]
  0.upto(7){|i| counterBlock[i] = ctrTxt.bytes.to_a[i]}

  #generate key Schedule
  keySchedule = Aes.keyExpansion(key);

  # separate ciphertext into blocks (skipping past initial 8 bytes)
  nBlocks = ((ciphertext.length-8)/blockSize.to_f).ceil
  ct=[]
  0.upto(nBlocks-1){|b|ct[b] = ciphertext[8+b*blockSize, 16]}

  ciphertext = ct;  # ciphertext is now array of block-length strings

  # plaintext will get generated block-by-block into array of block-length strings
  plaintxt = [];
  0.upto(nBlocks-1) do |b|
    0.upto(3){|c| counterBlock[15-c] = self.urs(b,c*8) & 0xff}
    0.upto(3){|c| counterBlock[15-c-4] = self.urs((b+1)/(0x100000000-1),c*8) & 0xff}
    cipherCntr = Aes.cipher(counterBlock, keySchedule)  # encrypt counter block
    plaintxtByte = []
    0.upto(ciphertext[b].length - 1) do |i|
      # -- xor plaintxt with ciphered counter byte-by-byte --
      plaintxtByte[i] = (cipherCntr[i] ^ ciphertext[b].bytes.to_a[i]).chr;
    end
    plaintxt[b] = plaintxtByte.join('')
  end
  plaintext = plaintxt.join('')
end

.encrypt(plaintext, password, nBits) ⇒ Object

@returns string Encrypted text



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/aesctr-ruby.rb', line 166

def self.encrypt(plaintext, password, nBits)
  blockSize = 16  # block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  return '' unless(nBits==128 || nBits==192 || nBits==256)

  # use AES itself to encrypt password to get cipher key (using plain password as source for key
  # expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
  nBytes = nBits/8  # no bytes in key (16/24/32)
  pwBytes = []
  0.upto(nBytes-1){|i| pwBytes[i] = password.bytes.to_a[i] & 0xff || 0} # use 1st 16/24/32 chars of password for key #warn
  key = Aes::cipher(pwBytes, Aes::keyExpansion(pwBytes)) # gives us 16-byte key
  key = key + key[0, nBytes-16] # expand key to 16/24/32 bytes long
  # initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec,
  # [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
  counterBlock = []
  nonce = Time.now.to_i #42200
  nonceMs = nonce%1000
  nonceSec = (nonce/1000.0).floor
  nonceRnd = (rand() *0xffff).floor #13
  0.upto(1){|i| counterBlock[i] = self.urs(nonceMs, i*8) &0xff }
  0.upto(1){|i| counterBlock[i+2] = self.urs(nonceRnd, i*8) &0xff }
  0.upto(3){|i| counterBlock[i+4] = self.urs(nonceSec,i*8) & 0xff}

  # and convert it to a string to go on the front of the ciphertext
  ctrTxt = ''
  0.upto(7){|i| ctrTxt += counterBlock[i].chr}
  # generate key schedule - an expansion of the key into distinct Key Rounds for each round
  keySchedule = Aes.keyExpansion(key)
#p "rks:",keySchedule
  blockCount = (plaintext.length/blockSize.to_f).ceil

  ciphertxt = []
  0.upto(blockCount-1) do |b|
    # set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
    # done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
    0.upto(3){|c| counterBlock[15-c] = self.urs(b,c*8) & 0xff}
    0.upto(3){|c| counterBlock[15-c-4] = self.urs(b/0x100000000,c*8)}

    cipherCntr = Aes.cipher(counterBlock, keySchedule) # -- encrypt counter block --
    # block size is reduced on final block
    blockLength =  b < blockCount-1 ? blockSize : (plaintext.length-1) % blockSize + 1
    cipherChar = [];
    0.upto(blockLength-1) do |i|
      cipherChar[i] = (cipherCntr[i] ^ plaintext.bytes.to_a[b*blockSize+i]).chr
    end
    ciphertxt[b] = cipherChar.join('')
  end

  ciphertext = ctrTxt + ciphertxt.join('')
  ciphertext = Base64.encode64(ciphertext).gsub(/\n/,'')+"\n";  # encode in base64
end