So I'm looking to encrypt files using AES CTR, using a shared key that would be accessed via a per-user password.

My two main concerns are:

  • making sure that the algorithms in use are appropriate for their use (proper entropy, used appropriately given the larger context)
  • that the persisted data won't (significantly) impact the strength of the encryption.

DISCLAIMER: I am also FULLY aware that I want to (personally) implement as LITTLE encryption as is possible, given all the ways that it can go wrong.

So, here's my process:

(fk1) Generate 128/192/256-bit AES file encryption key (using RNGCryptoServiceProvider)
  - obviously sensitive

User Account Creation:
- (uk1) Generate 1024-bit RSA pub/priv key (using RSACryptoServiceProvider)
  - public NOT sensitive, private IS sensitive
- (uh1) Generate 256-bit password salt (using RNGCryptoServiceProvider)
  - NOT treated as sensitive
- (uh2) Generate 512-bit password hash (using SHA512CryptoServiceProvider)
  - (EDIT: <formerly>using Rfc2898DeriveBytes with at least 1000 iterations</formerly>)
  - (uh2u) Upper/First 256 bits will be used as an AES key for later
    - treat as sensitive
  - (uh2l) Lower/Latter 256 bits used as standard PW hash, during password validation
    - NOT treated as sensitive
- (uk1iv) Generate 128-bit IV (using RNGCryptoServiceProvider)
    - NOT treated as sensitive
- (uk1pr) AES_CBC Encrypt (using AesCryptoServiceProvider) the RSA private key (uk1) using PW hash's upper 256 bits (uh2u) and previously generated IV (uk1iv)
  - being encrypted, NOT treated as sensitive
- (ufk1) RSA Encrypt (using RsaCryptoServiceProvider) the file encryption key (fk1) using RSA pub key (uk1)
  - being encrypted, NOT treated as sensitive

- Persist the following user data:
  - Password Salt (uh1)
  - Password Hash Lower 256 (uh2l)
  - RSA Public Key (uk1)
  - AES IV for RSA Private Key (uk1iv)
  - AES Encrypted RSA Private Key (uk1pr)
    - EDIT : will also include HMAC
  - RSA Encrypted AES file key (ufk1)

User Logon Process:
- Using password, confirm Password Hash Lower 256 (uh2l)
- Using PW hash Upper 256, decrypt RSA priv key
- Using RSA priv key, decrypt AES file key
- Keep AES file key in secure memory (using ProtectedMemory with CurrentProcess scope) for file encryption/decryption

File Encryption
- (fiv) Generate 128-bit IV (using RNGCryptoServiceProvider)
- AES Encrypt (CTR mode) file using AES file key (ufk1) from protected memory and previously generated IV (fiv)
  - being encrypted, NOT treated as sensitive
  - EDIT : file will also include HMAC

Also, since AESCryptoServiceProvider doesn't implement CTR mode (which for my needs, is a requirement, and the external libraries that I considered all seemed to have their own set of challenges), here's the pseudocode that I'm using:

dataBlockCount = ceiling(data.Length / 16)  // 128-bit block size
(ulong)BlockCounter = 1                     // seems that CTR should always start at 1, not 0
AESCSP = new AesCryptoServiceProvider
do {
  IV = nonce concat BlockCounter    // BlockCounter needs to be big-endian
  CurrentBlock = GetNextBlock(Data) // lesser of 16 bytes or remaining data

  // I didn't think I'd need to do this, but testing showed otherwise...
  IV = IV XOR CurrentBlock
  IV = AESCSP.CreateEncryptor(Key, IV).Encrypt(CurrentBlock)
  CipherBlock = IV XOR CurrentBlock
} while (BlockIndex < dataBlockCount)

While I'm not sure why I need to do any of the XOR'ing (I understand the AES side of input-block / CIPH / output-block, but I'm surprised that I can't just use nonce+counter as the IV for AesCryptoServiceProvider)... but using my code, I'm able to successfully validate against NIST's example vectors (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf) using both individual blocks, and chains of blocks... so I feel that it meets the "algorithm" requirements. I'm also under the impression that the AESCSP handles any sort of row shifting, which is CERTAINLY something that I'm happy not to implement myself.

I used some of the following as references:

In case of a non-random nonce (such as a packet counter), the nonce and counter should be concatenated (e.g. storing nonce in upper 64-bit and the counter in lower 64-bit).

The block counter field is the least significant 32 bits of the counter block this gives (2^32)-1 blocks = 4,294,967,296 since each block is 128-bit (16 byte), total counter space is 68,719,476,720 octets (bytes) = ~68gbyte

Sorry for the small novel; I tried to keep it as small as possible while providing what I thought would be necessary.

Many thanks in advance.

Additional notes/comments:

  • seems that the general consensus for counter overflow is to use a new nonce (as opposed to allowing the overflow and continuing, or by incrementing the nonce)... I'll just generate additional file keys, and switch from one to the next when the counter overflows (and reset counter to 1, not 0)

  • data integrity (HMAC/etc) will be applied, but stored elsewhere... tentatively planning on having file size and SHA512 for encrypted file (unsecured), then encrypting a copy of the unencrypted files' (SHA512) hash, along with some other details (file size of unencrypted, etc). Also, the encrypted files will be persisted across an SSL encrypted WAN/link.

  • if PBKDF2 isn't good for generating 512bit hashes, easy to switch to SHA512 (especially since the libraries already have the implementation)

  • bumping the iterations up is also easy, especially since it's only used during the login process (as opposed to the file encryption/decryption)

  • RSA was chosen on the basis that any user can create new file keys (each encrypted file will have metadata indicating which file key used for its encryption), and when doing so, the per-user-file-key (ukf1) can be created for each user using their pub key.

  • as far as the HMAC... the HMAC won't be stored far from the file... sorta like storing it in the NTFS alternative data stream as opposed to the file... not very far, but not directly in the file bytes... as far as why: a few reasons, some better than others...

    • one being double-blind verification (since the two are stored separately, any attempt to change the persisted copy would also require the hash calculations to be applied in both locations, which would require permission to both storage locations)... whether this is the ACTUAL improvement in security or just illusionary is unknown to me... I suppose for ACTUAL security the HMAC should be in BOTH locations. So, all this given, I'll probably adjust the process to include HMACs
  • changed PBKDF2 to SHA512, as result of How to derive two keys from one password (apparently, SO/MD won't let me use < s > within a code block, so I tagged the change with )

  • 3
    $\begingroup$ Seems like a sensible scheme to me if you 1. don't need integrity / authentication of the ciphertext and 2. if you really want to separate the encryption and decryption key (if you don't you may not need RSA). Note that using a 2048 bit or higher RSA key may be a good idea. ECIES would probably be faster. 1000 iterations is peanuts too. $\endgroup$
    – Maarten Bodewes
    Oct 22 '15 at 22:56
  • 3
    $\begingroup$ Oh, and you may want to use a separate KDF or SHA-512 for your password hashing. PBKDF2 is not designed too well for output over the hash size. $\endgroup$
    – Maarten Bodewes
    Oct 22 '15 at 23:04
  • $\begingroup$ @MaartenBodewes "PBKDF2 [...] over the hash size" .. you mean the salt size? the PBKDF2 provider gives me as large of a hash as I like. $\endgroup$ Oct 23 '15 at 0:28
  • 1
    $\begingroup$ @ScottBrickey, crypto.stackexchange.com/a/8253/13625 $\endgroup$
    – otus
    Oct 23 '15 at 5:19
  • $\begingroup$ "data integrity (HMAC/etc) will be applied, but stored elsewhere" -- This is unusual; may I ask why? $\endgroup$
    – Tim McLean
    Oct 23 '15 at 5:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.