/// <summary> /// Attempts to find an Inner Header and File Info object using the supplied decryption Keys. /// If the decryption keys do not decrypt the header, then they were not listed as a recipient. /// </summary> /// <param name="header">the ingested header object from the file</param> /// <param name="RecipientKeys">the Keys object with the key pair to attempt for decryption</param> /// <param name="Result">output: a FullHeader object with the fully decrypted file header details</param> /// <returns>false on any error, including (but not limited to): bad ciphertext, not a recipient</returns> private static bool TryDecryptHeader(HeaderInfo header, miniLockManaged.Keys RecipientKeys, out FullHeader Result) { Result = new FullHeader(); Result.UpdateFromHeader(header); UTF8Encoding utf8 = new UTF8Encoding(); foreach (string n in header.decryptInfo.Keys) // n is outer nonce { //System.Diagnostics.Debug.Print("decryptInfo Nonce: " + k); // DON'T LEAK!!! string v = header.decryptInfo[n]; //payload byte[] buffer = RecipientKeys.TryDecrypt(header.ephemeral, true, v.ToBytesFromBase64(), n.ToBytesFromBase64()); if (buffer != null) // looks like we're a recipient! proceed! if not just move on to the next one { string stuff = utf8.GetString(buffer); buffer.Wipe(); InnerHeaderInfo ih = InnerHeaderInfo.FromJSON(stuff); stuff = null; if (ih != null) { //System.Diagnostics.Debug.Print("_IH OBJECT=" + ih.ToJSON()); // DON'T LEAK!!! if (ih.recipientID != RecipientKeys.PublicID) { Result.Clear(); return(false); } if (!miniLockManaged.Keys.ValidatePublicKey(ih.senderID)) { Result.Clear(); return(false); } Result.UpdateFromInnerHeader(ih); buffer = ih.fileInfo.ToBytesFromBase64(); // use same nonce from OUTER buffer = RecipientKeys.TryDecrypt(ih.senderID, buffer, n.ToBytesFromBase64()); if (buffer != null) { stuff = utf8.GetString(buffer); buffer.Wipe(); FileInfo fi = FileInfo.FromJSON(stuff); if (fi != null) { Result.UpdateFromFileInfo(fi); //System.Diagnostics.Debug.Print("_FI OBJECT=" + fi.ToJSON()); // DON'T LEAK!!! return(true); } } } } } // either not a recipient, or something else went wrong Result.Clear(); return(false); }
public void UpdateFromInnerHeader(InnerHeaderInfo ih) { senderID = Keys.GetBytesFromPublicKey(ih.senderID); recipientID = Keys.GetBytesFromPublicKey(ih.recipientID); }
public static long EncryptFile(System.IO.FileInfo SourceFile, FileStream DestinationFile, string[] Recipients, Keys SenderKeys) { // crypto variables // THESE SHOULD BE RANDOM! byte[] fileNonce = Utilities.GenerateRandomBytes(16); byte[] fileKey = Utilities.GenerateRandomBytes(32); Keys ephemeral = new Keys(true); // these are dependant on recipients byte[] sharedKey = null; // validate parameters //process chunks Blake2sCSharp.Hasher b2s = Blake2sCSharp.Blake2S.Create(); UTF8Encoding utf8 = new UTF8Encoding(); // use cache file instead of a memory stream to conserve used memory MemoryStream ms = new MemoryStream(); // processed chunks go here string tempFile = null; FileStream cacheFs = GetTempFileStream(out tempFile); FileStream fs = new FileStream(SourceFile.FullName, FileMode.Open, FileAccess.Read); long fileCursor = 0; byte[] chunk = null; UInt64 chunkCount = 0; byte[] chunkNonce = new byte[24]; // always a constant length // this part of the nonce doesn't change Array.Copy(fileNonce, chunkNonce, fileNonce.Length); // copy it once and be done with it do { if (chunkCount == 0) // first chunk is always '\0'-padded filename { chunk = new byte[256]; byte[] filename = utf8.GetBytes(SourceFile.Name); Array.Copy(filename, chunk, filename.Length); filename.Wipe(); // DON'T LEAK!!! } else { if (fileCursor + MAX_CHUNK_SIZE >= SourceFile.Length) { // last chunk chunkNonce[23] |= 0x80; chunk = new byte[SourceFile.Length - fileCursor]; } else { chunk = new byte[MAX_CHUNK_SIZE]; } if (fs.Read(chunk, 0, chunk.Length) != chunk.Length) { // read error! fs.Close(); fs.Dispose(); TrashTempFileStream(cacheFs, tempFile); throw new System.IO.IOException("Abrupt end of file / read error from source."); } fileCursor += chunk.Length; } byte[] outBuffer = XSalsa20Poly1305.Encrypt(chunk, fileKey, chunkNonce); byte[] chunkLengthBytes = Utilities.UInt32ToBytes((uint)chunk.Length); cacheFs.Write(chunkLengthBytes, 0, 4); // use cache file b2s.Update(chunkLengthBytes); // hash as we go cacheFs.Write(outBuffer, 0, outBuffer.Length); // use cache file b2s.Update(outBuffer); // hash as we go // since the first chunkNonce is just the fileNonce and a bunch of 0x00's, // it's safe to do the chunk counter as a post-process update Utilities.UInt64ToBytes(++chunkCount, chunkNonce, 16); } while (fileCursor < SourceFile.Length); cacheFs.Flush(true); // make sure everything is flushed to the disk cache cacheFs.Position = 0; // leave it open so that we can read it back into the destination // get the ciphertext hash for the header byte[] cipherTextHash = b2s.Finish(); // done encrypting to the cache, now to build the header //build header (fileInfo needed first, but same for all recipients)... FileInfo fi = new FileInfo( fileKey.ToBase64String(), fileNonce.ToBase64String(), cipherTextHash.ToBase64String()); byte[] fiBytes = utf8.GetBytes(fi.ToJSON()); // encrypt this to the recipients next... //build inner headers next (one for each recipient) Dictionary <string, string> innerHeaders = new Dictionary <string, string>(Recipients.Length); foreach (string recip in Recipients) { // each recipient is not identified in the outer header, only a random NONCE byte[] recipientNonce = Utilities.GenerateRandomBytes(24); sharedKey = // INNER SHARED KEY (Sender Secret + Recipient Public) SenderKeys.GetShared(recip); InnerHeaderInfo ih = new InnerHeaderInfo( SenderKeys.PublicID, recip, XSalsa20Poly1305.Encrypt(fiBytes, sharedKey, recipientNonce).ToBase64String()); // fileInfo JSON object encrypted, Base64 sharedKey = // OUTER SHARED KEY (Ephemeral Secret + Recipient Public) ephemeral.GetShared(recip); string encryptedInnerHeader = ih.ToJSON(); encryptedInnerHeader = XSalsa20Poly1305.Encrypt(utf8.GetBytes(encryptedInnerHeader), sharedKey, recipientNonce).ToBase64String(); innerHeaders.Add(recipientNonce.ToBase64String(), encryptedInnerHeader); } // finally the outer header, ready for stuffing into the file HeaderInfo hi = new HeaderInfo(1, ephemeral.PublicKey.ToBase64String(), innerHeaders); string fileHeader = hi.ToJSON(); // build the final file... DestinationFile.Write(utf8.GetBytes("miniLock"), 0, 8); // file identifier (aka "magic bytes") DestinationFile.Write(Utilities.UInt32ToBytes((uint)fileHeader.Length), 0, 4); // header length in 4 little endian bytes DestinationFile.Write(utf8.GetBytes(fileHeader), 0, fileHeader.Length); // the full JSON header object // read back from the cache file into the destination file... byte[] buffer; for (int i = 0; i < cacheFs.Length; i += buffer.Length) { if (i + MAX_CHUNK_SIZE >= cacheFs.Length) { buffer = new byte[cacheFs.Length - i]; } else { buffer = new byte[MAX_CHUNK_SIZE]; } if (cacheFs.Read(buffer, 0, buffer.Length) != buffer.Length) { throw new System.IO.IOException("Abrupt end of cache file"); } DestinationFile.Write(buffer, 0, buffer.Length); // the ciphertext } // now flush and close, and grab length for reporting to caller DestinationFile.Flush(); long tempOutputFileLength = DestinationFile.Length; DestinationFile.Close(); DestinationFile.Dispose(); // kill the cache and the directory created for it TrashTempFileStream(cacheFs, tempFile); return(tempOutputFileLength); }