Skip to content

Instantly share code, notes, and snippets.

@zaus
Last active October 29, 2018 08:56
Show Gist options
  • Save zaus/c0ea1fd8dad5d9590af1 to your computer and use it in GitHub Desktop.
Save zaus/c0ea1fd8dad5d9590af1 to your computer and use it in GitHub Desktop.
/// <summary>
/// PGP round-trip encryption -- http://blogs.microsoft.co.il/kim/2009/01/23/pgp-zip-encrypted-files-with-c/#comment-611002
/// </summary>
public class PGPEncryptDecrypt
{
/**
* A simple routine that opens a key ring file and loads the first available key suitable for
* encryption.
*
* @param in
* @return
* @m_out
* @
*/
private static PgpPublicKey ReadPublicKey(Stream inputStream)
{
inputStream = PgpUtilities.GetDecoderStream(inputStream);
PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
//
// iterate through the key rings.
//
foreach (PgpPublicKeyRing kRing in pgpPub.GetKeyRings())
{
foreach (PgpPublicKey k in kRing.GetPublicKeys())
{
if (k.IsEncryptionKey)
{
return k;
}
}
}
throw new ArgumentException("Can't find encryption key in key ring.");
}
/**
* Search a secret key ring collection for a secret key corresponding to
* keyId if it exists.
*
* @param pgpSec a secret key ring collection.
* @param keyId keyId we want.
* @param pass passphrase to decrypt secret key with.
* @return
*/
private static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyId, char[] pass)
{
PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyId);
if (pgpSecKey == null)
{
return null;
}
return pgpSecKey.ExtractPrivateKey(pass);
}
/**
* decrypt the passed in message stream
*/
private static void DecryptStream(Stream inputStream, Stream keyIn, char[] passwd, Stream outputStream)
{
inputStream = PgpUtilities.GetDecoderStream(inputStream);
var pgpF = new PgpObjectFactory(inputStream);
PgpEncryptedDataList enc;
var o = pgpF.NextPgpObject();
//
// the first object might be a PGP marker packet.
//
if (o is PgpEncryptedDataList)
{
enc = (PgpEncryptedDataList)o;
}
else
{
enc = (PgpEncryptedDataList)pgpF.NextPgpObject();
}
//
// find the secret key
//
PgpPrivateKey sKey = null;
PgpPublicKeyEncryptedData pbe = null;
var pgpSec = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())
{
sKey = FindSecretKey(pgpSec, pked.KeyId, passwd);
if (sKey == null) continue;
pbe = pked;
break;
}
if (sKey == null)
{
throw new ArgumentException("secret key for message not found.");
}
var clear = pbe.GetDataStream(sKey);
var plainFact = new PgpObjectFactory(clear);
var message = plainFact.NextPgpObject();
if (message is PgpCompressedData)
{
var cData = (PgpCompressedData)message;
var pgpFact = new PgpObjectFactory(cData.GetDataStream());
message = pgpFact.NextPgpObject();
}
if (message is PgpLiteralData)
{
var ld = (PgpLiteralData)message;
//string outFileName = ld.FileName;
//if (outFileName.Length == 0)
//{
// outFileName = defaultFileName;
//}
using (outputStream)
{
var unc = ld.GetInputStream();
Streams.PipeAll(unc, outputStream);
}
}
else if (message is PgpOnePassSignatureList)
{
throw new PgpException("encrypted message contains a signed message -- not literal data.");
}
else
{
throw new PgpException("message is not a simple encrypted file -- type unknown.");
}
if (pbe.IsIntegrityProtected() && !pbe.Verify()) throw new PgpDataValidationException("Message failed integrity check");
}
private static void EncryptFile(Stream outputStream, FileInfo fileInfo, PgpPublicKey encKey, bool armor, bool withIntegrityCheck)
{
if (armor) outputStream = new ArmoredOutputStream(outputStream);
using (var buffer = new MemoryStream())
{
var compressor = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
PgpUtilities.WriteFileToLiteralData(
compressor.Open(buffer),
PgpLiteralData.Binary,
fileInfo);
compressor.Close();
var encryptor = new PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag.Cast5,
withIntegrityCheck,
new SecureRandom());
encryptor.AddMethod(encKey);
var bytes = buffer.ToArray();
using (var cOut = encryptor.Open(outputStream, bytes.Length))
{
cOut.Write(bytes, 0, bytes.Length);
}
}
if (armor) outputStream.Close();
}
#region ------- public methods --------
/// <summary>
/// File-based encryption
/// </summary>
/// <param name="filePath"></param>
/// <param name="publicKeyFile"></param>
/// <param name="outputFilePath"></param>
public static void Encrypt(string filePath, string publicKeyFile, string outputFilePath)
{
var fi = new FileInfo(filePath);
using (var keyIn = File.OpenRead(publicKeyFile))
using (var fos = File.Create(outputFilePath))
Encrypt(fi, keyIn, fos);
}
/// <summary>
/// File-based decryption
/// </summary>
/// <param name="filePath"></param>
/// <param name="privateKeyFile"></param>
/// <param name="passPhrase"></param>
/// <param name="pathToSaveFile"></param>
public static void Decrypt(string filePath, string privateKeyFile, string passPhrase, string pathToSaveFile)
{
using (var output = File.Create(pathToSaveFile))
using (var keyIn = File.OpenRead(privateKeyFile))
using (var fin = File.OpenRead(filePath))
Decrypt(fin, keyIn, passPhrase, output);
}
/// <summary>
/// Stream-based encryption, which relies on a local file for input source
/// </summary>
/// <param name="input">source to encrypt (local file)</param>
/// <param name="publicKey">the public key</param>
/// <param name="output">where to save the encrypted data</param>
public static void Encrypt(FileInfo input, Stream publicKey, Stream output)
{
EncryptFile(output, input, ReadPublicKey(publicKey), true, true);
}
/// <summary>
/// Stream-based decryption
/// </summary>
/// <param name="input">input source to decrypt</param>
/// <param name="privateKey">the private key</param>
/// <param name="passPhrase">the password to open the private key</param>
/// <param name="output">where to save the decrypted data</param>
public static void Decrypt(Stream input, Stream privateKey, string passPhrase, Stream output)
{
DecryptStream(input, privateKey, passPhrase.ToCharArray(), output);
}
public static void GenerateKey(string username, string password, string keyStoreUrl)
{
IAsymmetricCipherKeyPairGenerator kpg = GeneratorUtilities.GetKeyPairGenerator("RSA");
// new RsaKeyPairGenerator();
kpg.Init(new RsaKeyGenerationParameters(BigInteger.ValueOf(0x13), new SecureRandom(), 1024, 8));
AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();
FileStream out1 = new FileInfo(GetPrivateKeyPath(keyStoreUrl)).OpenWrite();
FileStream out2 = new FileInfo(GetPublicKeyPath(keyStoreUrl)).OpenWrite();
ExportKeyPair(out1, out2, kp.Public, kp.Private, PublicKeyAlgorithmTag.RsaGeneral, SymmetricKeyAlgorithmTag.Cast5, username, password.ToCharArray(), true);
out1.Close();
out2.Close();
/*
IAsymmetricCipherKeyPairGenerator dsaKpg = GeneratorUtilities.GetKeyPairGenerator("DSA");
DsaParametersGenerator pGen = new DsaParametersGenerator();
pGen.Init(1024, 80, new SecureRandom());
DsaParameters dsaParams = pGen.GenerateParameters();
DsaKeyGenerationParameters kgp = new DsaKeyGenerationParameters(new SecureRandom(), dsaParams);
dsaKpg.Init(kgp);
//
// this takes a while as the key generator has to Generate some DSA parameters
// before it Generates the key.
//
AsymmetricCipherKeyPair dsaKp = dsaKpg.GenerateKeyPair();
IAsymmetricCipherKeyPairGenerator elgKpg = GeneratorUtilities.GetKeyPairGenerator("ELGAMAL");
ElGamalParametersGenerator eGen = new ElGamalParametersGenerator();
eGen.Init(1024,80,new SecureRandom());
ElGamalParameters elParams = eGen.GenerateParameters();
ElGamalKeyGenerationParameters elKgp = new ElGamalKeyGenerationParameters(new SecureRandom(), elParams);
elgKpg.Init(elKgp);
//
// this is quicker because we are using preGenerated parameters.
//
AsymmetricCipherKeyPair elgKp = elgKpg.GenerateKeyPair();
FileStream out3 = new FileInfo(string.Format("{0}_PrivateKey_ELGMAL.txt", keyStoreUrl)).OpenWrite();
FileStream out4 = new FileInfo(string.Format("{0}_PublicKey_ELGMAL.txt", keyStoreUrl)).OpenWrite();
ExportKeyPair(out3, out4, dsaKp, elgKp, username, password.ToCharArray(), true);
out3.Close();
out4.Close();
*/
}
public void SignAndEncryptFile(string actualFileName,
string embeddedFileName,
Stream keyIn,
long keyId,
string OutputFileName,
char[] password,
bool armor,
bool withIntegrityCheck,
PgpPublicKey encKey)
{
const int BUFFER_SIZE = 1 << 16; // should always be power of 2
Stream outputStream = File.Open(OutputFileName, FileMode.Create);
if( armor )
outputStream = new ArmoredOutputStream(outputStream);
// Init encrypted data generator
PgpEncryptedDataGenerator encryptedDataGenerator =
new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());
encryptedDataGenerator.AddMethod(encKey);
Stream encryptedOut = encryptedDataGenerator.Open(outputStream, new byte[BUFFER_SIZE]);
// Init compression
PgpCompressedDataGenerator compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
Stream compressedOut = compressedDataGenerator.Open(encryptedOut);
// Init signature
PgpSecretKeyRingBundle pgpSecBundle = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
PgpSecretKey pgpSecKey = pgpSecBundle.GetSecretKey(keyId);
if( pgpSecKey == null )
throw new ArgumentException(keyId.ToString("X") + " could not be found in specified key ring bundle.", "keyId");
PgpPrivateKey pgpPrivKey = pgpSecKey.ExtractPrivateKey(password);
PgpSignatureGenerator signatureGenerator = new PgpSignatureGenerator(pgpSecKey.PublicKey.Algorithm,
HashAlgorithmTag.Sha1);
signatureGenerator.InitSign(PgpSignature.BinaryDocument, pgpPrivKey);
foreach (string userId in pgpSecKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
spGen.SetSignerUserId(false, userId);
signatureGenerator.SetHashedSubpackets(spGen.Generate());
// Just the first one!
break;
}
signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOut);
// Create the Literal Data generator output stream
PgpLiteralDataGenerator literalDataGenerator = new PgpLiteralDataGenerator();
FileInfo embeddedFile = new FileInfo(embeddedFileName);
FileInfo actualFile = new FileInfo(actualFileName);
// TODO: Use lastwritetime from source file
Stream literalOut = literalDataGenerator.Open(compressedOut,
PgpLiteralData.Binary,
embeddedFile.Name,
actualFile.LastWriteTime,
new byte[BUFFER_SIZE]);
// Open the input file
FileStream inputStream = actualFile.OpenRead();
byte[] buf = new byte[BUFFER_SIZE];
int len;
while( (len = inputStream.Read(buf, 0, buf.Length)) > 0 )
{
literalOut.Write(buf, 0, len);
signatureGenerator.Update(buf, 0, len);
}
literalOut.Close();
literalDataGenerator.Close();
signatureGenerator.Generate().Encode(compressedOut);
compressedOut.Close();
compressedDataGenerator.Close();
encryptedOut.Close();
encryptedDataGenerator.Close();
inputStream.Close();
if( armor )
outputStream.Close();
}
#endregion ------- public methods --------
private static void ExportKeyPair(
Stream secretOut,
Stream publicOut,
AsymmetricKeyParameter publicKey,
AsymmetricKeyParameter privateKey,
PublicKeyAlgorithmTag PublicKeyAlgorithmTag,
SymmetricKeyAlgorithmTag SymmetricKeyAlgorithmTag,
string identity,
char[] passPhrase,
bool armor)
{
if( armor )
{
secretOut = new ArmoredOutputStream(secretOut);
}
PgpSecretKey secretKey = new PgpSecretKey(
PgpSignature.DefaultCertification,
PublicKeyAlgorithmTag,
publicKey,
privateKey,
DateTime.Now,
identity,
SymmetricKeyAlgorithmTag,
passPhrase,
null,
null,
new SecureRandom()
// ,"BC"
);
secretKey.Encode(secretOut);
secretOut.Close();
if( armor )
{
publicOut = new ArmoredOutputStream(publicOut);
}
PgpPublicKey key = secretKey.PublicKey;
key.Encode(publicOut);
publicOut.Close();
}
private static void ExportKeyPair(
Stream secretOut,
Stream publicOut,
AsymmetricCipherKeyPair dsaKp,
AsymmetricCipherKeyPair elgKp,
string identity,
char[] passPhrase,
bool armor)
{
if( armor )
{
secretOut = new ArmoredOutputStream(secretOut);
}
PgpKeyPair dsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.Dsa, dsaKp, DateTime.UtcNow);
PgpKeyPair elgKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ElGamalEncrypt, elgKp, DateTime.UtcNow);
PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification,
dsaKeyPair,
identity,
SymmetricKeyAlgorithmTag.Aes256,
passPhrase,
true,
null,
null,
new SecureRandom());
keyRingGen.AddSubKey(elgKeyPair);
keyRingGen.GenerateSecretKeyRing().Encode(secretOut);
if( armor )
{
secretOut.Close();
publicOut = new ArmoredOutputStream(publicOut);
}
keyRingGen.GeneratePublicKeyRing().Encode(publicOut);
if( armor )
{
publicOut.Close();
}
}
// ================================== extra stuff
private const string GENERATED_PRIVATE_KEY_PATH_SUFFIX = "_PrivateKey.txt";
private const string GENERATED_PUBLIC_KEY_PATH_SUFFIX = "_PublicKey.txt";
public static string GetPublicKeyPath(string keyStoreUrl)
{
return string.Format("{0}{1}", keyStoreUrl, GENERATED_PUBLIC_KEY_PATH_SUFFIX);
}
public static string GetPrivateKeyPath(string keyStoreUrl)
{
return string.Format("{0}{1}", keyStoreUrl, GENERATED_PRIVATE_KEY_PATH_SUFFIX);
}
}
[TestClass]
public class PGPEncryptDecryptTests : UnitTestsBase
{
[TestMethod]
public void Pgp_RoundTrip()
{
var testname = System.Reflection.MethodBase.GetCurrentMethod().Name;
var passphrase = PASSWORD;
// make some keys just for this test
var keyPath = CreateAndConfirmKeys(testname);
// test file paths
var pubkeyFile = PGPEncryptDecrypt.GetPublicKeyPath(keyPath);
var privkeyFile = PGPEncryptDecrypt.GetPrivateKeyPath(keyPath);
var unencryptedSourceFile = getTestPath(testname, "unencrypted.txt");
var encryptedSourceFile = getTestPath(testname, "encrypted.txt");
var decryptedSourceFile = getTestPath(testname, "decrypted.txt");
// some random content
var testRoot = getTestPath(testname);
if(!Directory.Exists(testRoot)) Directory.CreateDirectory(testRoot);
var content = Faker.TextFaker.Sentences(5);
File.WriteAllText(unencryptedSourceFile, content);
this.Dump("↓ Original Source Text at {0}:", unencryptedSourceFile);
this.Dump(content);
// encrypt
PGPEncryptDecrypt.Encrypt(unencryptedSourceFile, pubkeyFile, encryptedSourceFile);
Assert.IsTrue(File.Exists(encryptedSourceFile), "Did not make an encrypted file");
// decrypt
PGPEncryptDecrypt.Decrypt(encryptedSourceFile, privkeyFile, passphrase, decryptedSourceFile);
Assert.IsTrue(File.Exists(decryptedSourceFile), "Did not make an decrypted file");
// confirm
var decryptedContent = File.ReadAllText(decryptedSourceFile);
this.Dump(decryptedContent);
this.Dump("↑ Decrypted Source Text from {0}:", decryptedSourceFile);
Assert.AreEqual(content, decryptedContent, "Decrypted content doesn't match original");
// cleanup
File.Delete(pubkeyFile);
File.Delete(privkeyFile);
File.Delete(unencryptedSourceFile);
File.Delete(encryptedSourceFile);
File.Delete(decryptedSourceFile);
}
private const string USERNAME = "theusername";
private const string PASSWORD = "thepassword";
private const string OUTPUT_FOLDER = @"..\..\files\";
[TestMethod]
public void Pgp_GenerateKey()
{
string subfolder = string.Empty;
var keyPath = CreateAndConfirmKeys(subfolder);
var pubkeyFile = PGPEncryptDecrypt.GetPublicKeyPath(keyPath);
var privkeyFile = PGPEncryptDecrypt.GetPrivateKeyPath(keyPath);
// cleanup
File.Delete(pubkeyFile);
File.Delete(privkeyFile);
}
/// <summary>
/// Where all the test files live
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private static string getTestPath(params string[] path)
{
if(null == path || !path.Any()) throw new ArgumentNullException("path");
var newlist = new string[path.Length + 1];
newlist[0] = OUTPUT_FOLDER;
path.CopyTo(newlist, 1);
return Path.Combine(newlist);
}
/// <summary>
/// Create public/private keys in the given subfolder
/// </summary>
/// <param name="subfolder"></param>
/// <returns>returns the full path to where the keys are located (for use by <see cref="PGPEncryptDecrypt.GetPublicKeyPath"/> and <see cref="PGPEncryptDecrypt.GetPrivateKeyPath"/></returns>
private static string CreateAndConfirmKeys(string subfolder)
{
var destPath = getTestPath(subfolder);
PGPEncryptDecrypt.GenerateKey(USERNAME, PASSWORD, destPath);
var expectedPath = PGPEncryptDecrypt.GetPublicKeyPath(destPath);
Assert.IsTrue(File.Exists(expectedPath), "Public key not created at {0}", expectedPath);
expectedPath = PGPEncryptDecrypt.GetPrivateKeyPath(destPath);
Assert.IsTrue(File.Exists(expectedPath), "Private key not created at {0}", expectedPath);
return destPath;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment