Implement C# for Encryption/Decryption by PGP
จากบทความที่แล้ว เราได้สร้าง 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
- https://stackoverflow.com/questions/4192296/c-sharp-how-to-simply-encrypt-a-text-file-with-a-pgp-public-key
- https://www.codeproject.com/Questions/69856/Encrypting-and-Decrypting-files-with-PGP-and-C
- https://stackoverflow.com/questions/25441366/bouncycastle-in-memory-pgp-encryption-in-c-sharp
- https://stackoverflow.com/questions/10209291/pgp-encrypt-and-decrypt