Implement C# for Encryption/Decryption by PGP
By: Alisara Hincheeranan
Tags: Pgpencryption, Pgp, C Sharp Programming

จากบทความที่แล้ว เราได้สร้าง PGP Public Key / Private Key ด้วย Kleopatra กันแล้ว ในบทความนี้เราจะพัฒนาระบบ Encryption/Decryption ด้วยภาษา C# กัน

Go to -> Visual Studio 2019 Choose -> Console App (.NET Framework ) -> .NET Framework 4.7.2 Right Click on Project -> Manage Nuget Package

Install BouncyCastle

Source Code Structure

PGPEncyptDecrypt.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.IO;

using Org.BouncyCastle.Bcpg;

using Org.BouncyCastle.Bcpg.OpenPgp;

using Org.BouncyCastle.Security;

using Org.BouncyCastle.Utilities.IO;

namespace PGPNET1

{

public static class PGPEncryptDecrypt

{

private const int BufferSize = 0x10000;

public static void EncryptFile(string inputFile, string outputFile, string publicKeyFile, bool armor, bool withIntegrityChec)

{

try

{

using (Stream publicKeyStream = File.OpenRead(publicKeyFile))

{

PgpPublicKey encKey = ReadPublicKey(publicKeyStream);

using (MemoryStream bOut = new MemoryStream())

{

PgpCompressedDataGenerator comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);

PgpUtilities.WriteFileToLiteralData(comData.Open(bOut), PgpLiteralData.Binary, new FileInfo(inputFile));

comData.Close();

PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityChec, new SecureRandom());

cPk.AddMethod(encKey);

byte[] bytes = bOut.ToArray();

using(Stream outputStream = File.Create(outputFile))

{

if(armor)

{

using(ArmoredOutputStream armoredStream = new ArmoredOutputStream(outputStream))

{

using(Stream cOut = cPk.Open(armoredStream, bytes.Length))

{

cOut.Write(bytes, 0, bytes.Length);

}

}

}

else

{

using(Stream cOut = cPk.Open(outputStream, bytes.Length))

{

cOut.Write(bytes, 0, bytes.Length);

}

}

}

}

}

}

catch (PgpException e)

{

throw;

}

}

public static void EncryptAndSign(string inputFile,string outputFile, string publicKeyFile,string privateKeyFile,string passPhrase, bool armor)

{

PgpEncryptionKeys encryptionKeys = new PgpEncryptionKeys(publicKeyFile, privateKeyFile, passPhrase);

if (!File.Exists(inputFile))

throw new FileNotFoundException(String.Format(“Input file [{0}] does not exist.”, inputFile));

if (!File.Exists(publicKeyFile))

throw new FileNotFoundException(String.Format(“Public Key file [{0}] does not exist.”, publicKeyFile));

if (!File.Exists(privateKeyFile))

throw new FileNotFoundException(String.Format(“Private Key file [{0}] does not exist.”, privateKeyFile));

if (String.IsNullOrEmpty(passPhrase))

throw new ArgumentNullException(“Invalid Pass Phrase.”);

if (encryptionKeys == null)

throw new ArgumentNullException(“Encryption Key not found.”);

using (Stream outputStream = File.Create(outputFile))

{

if (armor)

using (ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(outputStream))

{

OutputEncrypted(inputFile, armoredOutputStream, encryptionKeys);

}

else

OutputEncrypted(inputFile, outputStream, encryptionKeys);

}

}

private static void OutputEncrypted(string inputFile,Stream outputStream, PgpEncryptionKeys encryptionKeys)

{

using (Stream encryptedOut = ChainEncryptedOut(outputStream, encryptionKeys))

{

FileInfo unencryptedFileInfo = new FileInfo(inputFile);

using (Stream compressedOut = ChainCompressedOut(encryptedOut))

{

PgpSignatureGenerator signatureGenerator = InitSignatureGenerator(compressedOut, encryptionKeys);

using (Stream literalOut = ChainLiteralOut(compressedOut, unencryptedFileInfo))

{

using (FileStream inputFileStream = unencryptedFileInfo.OpenRead())

{

WriteOutputAndSign(compressedOut, literalOut, inputFileStream, signatureGenerator);

inputFileStream.Close();

}

}

}

}

}

private static void WriteOutputAndSign(Stream compressedOut, Stream literalOut, FileStream inputFile, PgpSignatureGenerator signatureGenerator)

{

int length = 0;

byte[] buf = new byte[BufferSize];

while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)

{

literalOut.Write(buf, 0, length);

signatureGenerator.Update(buf, 0, length);

}

signatureGenerator.Generate().Encode(compressedOut);

}

private static Stream ChainEncryptedOut(Stream outputStream, PgpEncryptionKeys m_encryptionKeys)

{

PgpEncryptedDataGenerator encryptedDataGenerator;

encryptedDataGenerator = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.TripleDes, new SecureRandom());

encryptedDataGenerator.AddMethod(m_encryptionKeys.PublicKey);

return encryptedDataGenerator.Open(outputStream, new byte[BufferSize]);

}

private static Stream ChainCompressedOut(Stream encryptedOut)

{

PgpCompressedDataGenerator compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);

return compressedDataGenerator.Open(encryptedOut);

}

private static Stream ChainLiteralOut(Stream compressedOut, FileInfo file)

{

PgpLiteralDataGenerator pgpLiteralDataGenerator = new PgpLiteralDataGenerator();

return pgpLiteralDataGenerator.Open(compressedOut, PgpLiteralData.Binary, file);

}

private static PgpSignatureGenerator InitSignatureGenerator(Stream compressedOut, PgpEncryptionKeys m_encryptionKeys)

{

const bool IsCritical = false;

const bool IsNested = false;

PublicKeyAlgorithmTag tag = m_encryptionKeys.SecretKey.PublicKey.Algorithm;

PgpSignatureGenerator pgpSignatureGenerator = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);

pgpSignatureGenerator.InitSign(PgpSignature.BinaryDocument, m_encryptionKeys.PrivateKey);

foreach (string userId in m_encryptionKeys.SecretKey.PublicKey.GetUserIds())

{

PgpSignatureSubpacketGenerator subPacketGenerator = new PgpSignatureSubpacketGenerator();

subPacketGenerator.SetSignerUserId(IsCritical, userId);

pgpSignatureGenerator.SetHashedSubpackets(subPacketGenerator.Generate());

// Just the first one!

break;

}

pgpSignatureGenerator.GenerateOnePassVersion(IsNested).Encode(compressedOut);

return pgpSignatureGenerator;

}

/*

* decrypt a given stream.

*/

public static void Decrypt(string inputfile, string privateKeyFile, string passPhrase, string outputFile)

{

if (!File.Exists(inputfile))

throw new FileNotFoundException(String.Format(“Encrypted File [{0}] not found.”, inputfile));

if (!File.Exists(privateKeyFile))

throw new FileNotFoundException(String.Format(“Private Key File [{0}] not found.”, privateKeyFile));

if (String.IsNullOrEmpty(outputFile))

throw new ArgumentNullException(“Invalid Output file path.”);

using (Stream inputStream = File.OpenRead(inputfile))

{

using (Stream keyIn = File.OpenRead(privateKeyFile))

{

Decrypt(inputStream, keyIn, passPhrase, outputFile);

}

}

}

/*

* decrypt a given stream.

*/

public static void Decrypt(Stream inputStream, Stream privateKeyStream, string passPhrase, string outputFile)

{

try

{

PgpObjectFactory pgpF = null;

PgpEncryptedDataList enc = null;

PgpObject o = null;

PgpPrivateKey sKey = null;

PgpPublicKeyEncryptedData pbe = null;

PgpSecretKeyRingBundle pgpSec = null;

pgpF = new PgpObjectFactory(PgpUtilities.GetDecoderStream(inputStream));

// find secret key

pgpSec = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(privateKeyStream));

if (pgpF != null)

o = pgpF.NextPgpObject();

// the first object might be a PGP marker packet.

if (o is PgpEncryptedDataList)

enc = (PgpEncryptedDataList)o;

else

enc = (PgpEncryptedDataList)pgpF.NextPgpObject();

// decrypt

foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects())

{

sKey = FindSecretKey(pgpSec, pked.KeyId, passPhrase.ToCharArray());

if (sKey != null)

{

pbe = pked;

break;

}

}

if (sKey == null)

throw new ArgumentException(“Secret key for message not found.”);

PgpObjectFactory plainFact = null;

using (Stream clear = pbe.GetDataStream(sKey))

{

plainFact = new PgpObjectFactory(clear);

}

PgpObject message = plainFact.NextPgpObject();

if (message is PgpCompressedData)

{

PgpCompressedData cData = (PgpCompressedData)message;

PgpObjectFactory of = null;

using (Stream compDataIn = cData.GetDataStream())

{

of = new PgpObjectFactory(compDataIn);

}

message = of.NextPgpObject();

if (message is PgpOnePassSignatureList)

{

message = of.NextPgpObject();

PgpLiteralData Ld = null;

Ld = (PgpLiteralData)message;

using (Stream output = File.Create(outputFile))

{

Stream unc = Ld.GetInputStream();

Streams.PipeAll(unc, output);

}

}

else

{

PgpLiteralData Ld = null;

Ld = (PgpLiteralData)message;

using (Stream output = File.Create(outputFile))

{

Stream unc = Ld.GetInputStream();

Streams.PipeAll(unc, output);

}

}

}

else if (message is PgpLiteralData)

{

PgpLiteralData ld = (PgpLiteralData)message;

string outFileName = ld.FileName;

using (Stream fOut = File.Create(outputFile))

{

Stream unc = ld.GetInputStream();

Streams.PipeAll(unc, fOut);

}

}

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.”);

}

catch (PgpException ex)

{

throw;

}

}

private static PgpPublicKey ReadPublicKey(Stream inputStream)

{

inputStream = PgpUtilities.GetDecoderStream(inputStream);

PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);

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.”);

}

private static PgpPrivateKey FindSecretKey(PgpSecretKeyRingBundle pgpSec, long keyId, char[] pass)

{

PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyId);

if (pgpSecKey == null)

return null;

return pgpSecKey.ExtractPrivateKey(pass);

}

}

}

PgpEncryptionKeys.cs

using Org.BouncyCastle.Bcpg.OpenPgp;

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace PGPNET1

{

public class PgpEncryptionKeys

{

public PgpPublicKey PublicKey { get; set; }

public PgpPrivateKey PrivateKey { get; set; }

public PgpSecretKey SecretKey { get; set; }

public PgpEncryptionKeys(string publicKeyPath, string privateKeyPath, string passPhrase)

{

if (!File.Exists(publicKeyPath))

throw new ArgumentException(“Public key file not found”, “publicKeyPath”);

if (!File.Exists(privateKeyPath))

throw new ArgumentException(“Private key file not found”, “privateKeyPath”);

if (String.IsNullOrEmpty(passPhrase))

throw new ArgumentException(“passPhrase is null or empty.”, “passPhrase”);

//PublicKey = ReadPublicKey(publicKeyPath);

//PrivateKey = ReadPrivateKey(privateKeyPath);

//SecretKey = ReadSecretKey(passPhrase);

PublicKey = ReadPublicKey(publicKeyPath);

SecretKey = ReadSecretKey(privateKeyPath);

PrivateKey = ReadPrivateKey(passPhrase);

}

private PgpSecretKey ReadSecretKey(string privateKeyPath)

{

using (Stream keyIn = File.OpenRead(privateKeyPath))

{

using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn))

{

PgpSecretKeyRingBundle secretKeyRingBundle = new PgpSecretKeyRingBundle(inputStream);

PgpSecretKey foundKey = GetFirstSecretKey(secretKeyRingBundle);

if (foundKey != null)

return foundKey;

}

}

throw new ArgumentException(“Can’t find signing key in key ring.”);

}

private PgpSecretKey GetFirstSecretKey(PgpSecretKeyRingBundle secretKeyRingBundle)

{

foreach(PgpSecretKeyRing kRing in secretKeyRingBundle.GetKeyRings())

{

PgpSecretKey key = kRing.GetSecretKeys()

.Cast<PgpSecretKey>()

.Where(k => k.IsSigningKey)

.FirstOrDefault();

if (key != null)

return key;

}

return null;

}

private PgpPublicKey ReadPublicKey(string publicKeyPath)

{

using (Stream keyIn = File.OpenRead(publicKeyPath))

{

using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn))

{

PgpPublicKeyRingBundle publicKeyRingBundle = new PgpPublicKeyRingBundle(inputStream);

PgpPublicKey foundKey = GetFirstPublicKey(publicKeyRingBundle);

if (foundKey != null)

return foundKey;

}

}

throw new ArgumentException(“No encryption key found in public key ring.”);

}

private PgpPublicKey GetFirstPublicKey(PgpPublicKeyRingBundle publicKeyRingBundle)

{

foreach (PgpPublicKeyRing kRing in publicKeyRingBundle.GetKeyRings())

{

PgpPublicKey key = kRing.GetPublicKeys()

.Cast<PgpPublicKey>()

.Where(k => k.IsEncryptionKey)

.FirstOrDefault();

if (key != null)

return key;

}

return null;

}

private PgpPrivateKey ReadPrivateKey(string passPhrase)

{

PgpPrivateKey privateKey = SecretKey.ExtractPrivateKey(passPhrase.ToCharArray());

if (privateKey != null)

return privateKey;

throw new ArgumentException(“No private key found in secret key.”);

}

}

}

Program.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace PGPNET1

{

class Program

{

static void Main(string[] args)

{

PGPEncryptDecrypt.EncryptFile(@”D:\PGPProject\TESTDATA.txt”, @”D:\PGPProject\TESTDATAEncrypt.pgp”, @”D:\PGPProject\PublicKey\TESTGPG_0xCBEDD228_public.asc”, false, true);

//PGPEncryptDecrypt.Decrypt(@”D:\PGPProject\TESTDATAEncrypt.pgp”, @”D:\PGPProject\PrivateKey\TESTGPG_0xCBEDD228_SECRET.asc”, “Test@1234”, @”D:\PGPProject\TESTDATA_Out.txt”);

}

}

}

ทดสอบกันเถอะ !!!!

1.เราจะทำการ Encryt TESTDATA

Run:

PGPEncryptDecrypt.EncryptFile(@”D:\PGPProject\TESTDATA.txt”, @”D:\PGPProject\TESTDATAEncrypt.pgp”, @”D:\PGPProject\PublicKey\TESTGPG_0xCBEDD228_public.asc”, false, true);

Result: จะได้ TESTDATAEncrypt.pgp

TESTDATA.txt ก่อนการ Encrypt หน้าตาจะประมาณนี้

ลองเปิดไฟล์ TESTDATAEncrypt.pgp (ภาษาไรไม่รุ้ไม่รุ้เรื่อง)

2. Decrypt: TESTDATAEncrypt.pgp

Run:

PGPEncryptDecrypt.Decrypt(@”D:\PGPProject\TESTDATAEncrypt.pgp”, @”D:\PGPProject\PrivateKey\TESTGPG_0xCBEDD228_SECRET.asc”, “Test@1234”, @”D:\PGPProject\TESTDATA_Out.txt”);

Result:

TESTDATA_OUT.txt คือไฟล์ที่ถูก Decrypt เรีนบร้อยแล้ส

TESTDATA_OUT.txt หน้าตาประมาณนี้

สรุป

บทความนี้น่าจะช่วยอธิบายการ Encryption/Decryption by PGP ด้วย C# ให้รายๆคนที่มองหาวิธีในการ Encryption/Decryption ทั้งไฟล์ได้

References