Class: RepoInternal::MyAES
- Inherits:
-
Object
- Object
- RepoInternal::MyAES
- Defined in:
- lib/lenc/aes.rb
Overview
Wrapper for OpenSSL AES cipher.
Usage for encryption:
----------------------------------------------------
original = "..." # bytes to encrypt (string)
key = "xxxx..." # encryption key (string of size 8..56)
en = MyAES.new(true, key) # construct an encryptor
en.finish(original) # add data to encrypt
encrypted = en.flush() # get encrypted bytes (string)
----------------------------------------------------
Usage for decryption:
----------------------------------------------------
encrypted = "..." # bytes to decrypt
key = "xxxx...."
de = MyAES.new(false, key) # construct a decryptor
de.finish(original) # add data to decrypt
decrypted = de.flush() # get decrypted bytes
----------------------------------------------------
Use of nonces:
--------------
The above encryption example generates a new (hopefully unique) 'nonce'
which is an added security feature. It uses the system clock to do this.
This means the same file will produce different encrypted byte streams on
repeated encryption attempts, which may be undesirable. A fixed nonce
(for a particular input file) can be specified as an additional input:
nonce = "nnnn.." # only the first 8 bytes are used
en = MyAES.new(true, key, nonce)
The nonce, whether explicitly given or randomly generated, is added to the
encrypted stream; hence it need not be specified when decrypting.
Stream mode:
------------
When processing large files, you may want to do them a chunk at a time.
Here's an example of encrypting using stream mode (decrypting is similar):
en = MyAES.new(true, key)
s = {size of input file}
n = 0
while n < s
c = [5000, s - n].min
en.add( {bytes n..n+c-1 from the input file} )
r = en.flush()
{append bytes r to output file}
end
en.finish()
r = en.flush()
{append bytes r to output file}
----------------------------------------------------
Format of encrypted data:
[8] nonce (only the first 8 bytes of the nonce are actually used)
Followed by one or more encrypted chunks of length [k], where k is 65536, unless it's the last
chunk in the file, in which case it must be a multiple of 16.
The first bytes of each decrypted chunk is a header:
[7] zeros
[1] number of padding bytes present at end of block
For example, suppose a file of 71980 'source' bytes has been encrypted. The encrypted file will contain:
[8] nonce
[65536] first chunk, consisting of
[7] zeros
[1] zero, since this chunk needed no padding
[65528] 65528 encrypted source bytes
[6464] second chunk, consisting of
[7] zeros
[1] 4, indicating 4 padding bytes
[6456] 6452 encrypted source bytes plus 4 padding bytes
Observe that 65528 + 6452 = 71980.
The purpose of the [7] zeros in the (decrypted) chunk header are to indicate
whether decryption was successful (e.g., if the password was correct). The assumption
is that an incorrect password will generate 7 zeros in these locations with extremely low probability.
The byte used as a padding byte is 254.
If a file has length zero, then when encrypted, it will have the following structure:
[8] nonce
[16] chunk:
[7] zeros
[1] 8, indicating 8 padding bytes
[8] 0 encrypted source bytes plus 8 padding bytes
Instance Attribute Summary collapse
-
#prefix_mode ⇒ Object
Returns the value of attribute prefix_mode.
Class Method Summary collapse
-
.is_file_encrypted(key, path) ⇒ Object
Determines if a file is an encrypted file for the given password, and the file is of the expected length.
-
.is_string_encrypted(key, test_str) ⇒ Object
Determines if a string is the start of an encrypted sequence.
Instance Method Summary collapse
-
#add(data) ⇒ Object
Process additional input bytes, encrypting (or decrypting) its contents.
-
#finish(data = nil) ⇒ Object
Stop the encryption/decryption process.
-
#flush ⇒ Object
Return any output bytes that have been generated since the last call to flush().
-
#strip_encryption_header(encr_str) ⇒ Object
Strip the header from an encrypted string.
Instance Attribute Details
#prefix_mode ⇒ Object
Returns the value of attribute prefix_mode.
145 146 147 |
# File 'lib/lenc/aes.rb', line 145 def prefix_mode @prefix_mode end |
Class Method Details
.is_file_encrypted(key, path) ⇒ Object
Determines if a file is an encrypted file for the given password, and the file is of the expected length.
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/lenc/aes.rb', line 489 def self.is_file_encrypted(key, path) db = warndb 0 !db || pr("is_file_encrypted '#{path}'?\n") # key = str_to_bytes(key) if not File.file?(path) !db || pr(" not a file\n") return false end lnth = File.size(path) minSize = NONCE_SIZE_SMALL + AES_BLOCK_SIZE !db || pr(" file size=#{lnth}, minSize=#{minSize}\n") if lnth < minSize or ((lnth - minSize) % AES_BLOCK_SIZE) != 0 !db || pr(" length not appropriate\n") return false end if false warn("using full size of file") minSize = lnth end f = File.open(path,"rb") s = f.read(minSize) ret = is_string_encrypted(key, s) !db || pr(" is_string_encrypted returning #{ret}\n") ret end |
.is_string_encrypted(key, test_str) ⇒ Object
Determines if a string is the start of an encrypted sequence
Returns true iff the start of the string seems to decrypt correctly for the given password
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/lenc/aes.rb', line 440 def self.is_string_encrypted(key, test_str) db = warndb 0 !db || hex_dump(test_str, "is_string_encrypted?") simple_str(test_str) lnth = test_str.size lnth -= NONCE_SIZE_SMALL if lnth < AES_BLOCK_SIZE || lnth % AES_BLOCK_SIZE != 0 !db || pr(" bad # bytes\n") return false end hdr_size = AES_BLOCK_SIZE # This method is failing, I suspect because with the mode of AES we're using (CRC?) we can't # decrypt only a single block, and must instead decrypt a complete chunk. # No, now I think it's interpreting a bad 'padding' value (due to only decrypting partially) # as indication of bad decryption if false warn("using full chunk size") hdr_size = [lnth,CHUNK_SIZE_ENCR].min end begin de = MyAES.new(false, key) # Put this decryptor into prefix mode, so that we are only interested # in whether the header verifies correctly de.prefix_mode = true de.finish(test_str[0...hdr_size + NONCE_SIZE_SMALL]) de.flush() rescue LEnc::DecryptionError => e !db || pr(" (caught DecryptionError #{e})\n") return false end true end |
Instance Method Details
#add(data) ⇒ Object
Process additional input bytes, encrypting (or decrypting) its contents
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/lenc/aes.rb', line 350 def add(data) raise IllegalStateException if @finished simple_str(data) @inputBuffer << data #.concat(data) while true if not @encrypting # Extract nonce if we're waiting for it and it is now available if @decryptState == DS_WAITNONCE break if @inputBuffer.size < NONCE_SIZE_SMALL setNonce(@inputBuffer.slice!(0...NONCE_SIZE_SMALL)) @decryptState = DS_WAITCHUNK next end # If we don't have a full chunk, exit # (the last chunk may be smaller; we'll test for this when finishing up) break if @inputBuffer.size < CHUNK_SIZE_DECR else break if @inputBuffer.size < CHUNK_SIZE_ENCR end # Process chunk and repeat processChunk() end end |
#finish(data = nil) ⇒ Object
Stop the encryption/decryption process.
Processes any bytes that may have been buffered (since encryption occurs in 16 byte blocks at a time).
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/lenc/aes.rb', line 389 def finish(data = nil) add(data) if data raise IllegalStateException if @finished @finished = true inpLen = @inputBuffer.size if @encrypting # If input buffer is not empty, or we haven't written a first chunk (which contains the nonce), # encrypt a chunk if inpLen or (not @nonceWritten) processChunk() end else # We must be at WAITCHUNK with an input buffer that is a multiple of _AES_BLOCK_SIZE bytes in length if @decryptState != DS_WAITCHUNK or 0 != (inpLen & (AES_BLOCK_SIZE-1)) raise LEnc::DecryptionError, "decrypt state problem" end # We expect a chunk if there's more input, or if we've never processed a chunk. if inpLen != 0 or @chunkCount == 0 processChunk() end end end |
#flush ⇒ Object
Return any output bytes that have been generated since the last call to flush()
422 423 424 425 426 |
# File 'lib/lenc/aes.rb', line 422 def flush() ret = @outputBuffer @outputBuffer = '' return ret end |
#strip_encryption_header(encr_str) ⇒ Object
Strip the header from an encrypted string
429 430 431 |
# File 'lib/lenc/aes.rb', line 429 def strip_encryption_header( encr_str) return encr_str[CHUNK_HEADER_SIZE..-1] end |