GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
Cryptography.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using DocumentFormat.OpenXml.Drawing;
19using DocumentFormat.OpenXml.ExtendedProperties;
20using DocumentFormat.OpenXml.Math;
21using Org.BouncyCastle.Asn1.Pkcs;
22using Org.BouncyCastle.Asn1.X509;
23using Org.BouncyCastle.Crypto;
24using Org.BouncyCastle.Crypto.Engines;
25using Org.BouncyCastle.Crypto.Generators;
26using Org.BouncyCastle.Crypto.Modes;
27using Org.BouncyCastle.Crypto.Paddings;
28using Org.BouncyCastle.Crypto.Parameters;
29using Org.BouncyCastle.Pkcs;
30using Org.BouncyCastle.Security;
31using Org.BouncyCastle.X509;
32using static GenerallyPositive.Enums;
33
34namespace GenerallyPositive
35{
36 public class Cryptography : ICryptography
37 {
38 private CryptoAlgorithm _algorithm = CryptoAlgorithm.AES; // Default
39 private byte[] _key;
40 private byte[] _iv;
41 private CryptoMode _mode = CryptoMode.GCM; // Default
42 private CryptoPadding _padding = CryptoPadding.PKCS7; // Default
43 private byte[] _salt;
44 private int _iterationCount = 10000; // Default for PBKDF2
45 private byte[] _inputData;
46 private byte[] _signature; // Added for verification
47
48 internal Cryptography()
49 { }
50
51 public IAllowCryptoConfig WithAlgorithm(CryptoAlgorithm alg)
52 {
53 _algorithm = alg;
54 return this;
55 }
56
57 public IAllowCryptoConfig WithKey(byte[] key)
58 {
59 _key = key;
60 return this;
61 }
62
63 public IAllowCryptoConfig WithIV(byte[] iv)
64 {
65 _iv = iv;
66 return this;
67 }
68
69 public IAllowCryptoConfig WithMode(CryptoMode mode)
70 {
71 _mode = mode;
72 return this;
73 }
74
75 public IAllowCryptoConfig WithPadding(CryptoPadding padding)
76 {
77 _padding = padding;
78 return this;
79 }
80
81 public IAllowCryptoConfig WithSalt(byte[] salt)
82 {
83 _salt = salt;
84 return this;
85 }
86
87 public IAllowCryptoConfig WithSignature(byte[] signature)
88 {
89 _signature = signature;
90 return this;
91 }
92 public IAllowCryptoConfig WithIterationCount(int count)
93 {
94 if (count <= 0)
95 {
96 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Iteration count [{count}] is invalid, using default [{_iterationCount}]", this, GPALObjectType.Cryptography);
97 return this;
98 }
99 _iterationCount = count;
100 return this;
101 }
102
103 public IAllowCryptoAction WithInputData(byte[] data)
104 {
105 _inputData = data;
106 return this;
107 }
108
109 public IAllowCryptoConfig GenerateIV(out byte[] iv)
110 {
111 iv = null;
112 if (_algorithm != CryptoAlgorithm.AES)
113 {
114 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported for IV generation, defaulting to AES", this, GPALObjectType.Cryptography);
115 _algorithm = CryptoAlgorithm.AES;
116 }
117 if (_mode == CryptoMode.ECB)
118 {
119 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "IV generation not required for ECB mode", this, GPALObjectType.Cryptography);
120 return this;
121 }
122
123 GPAL.PublishSimpleEvent(GPALEventType.INFO,
124 $"Generating IV for algorithm [{_algorithm}], mode [{_mode}]",
125 this, GPALObjectType.Cryptography);
126
127 try
128 {
129 int ivSize = _mode == CryptoMode.GCM ? 12 : 16; // 12 bytes for GCM, 16 for CBC
130 iv = new byte[ivSize];
131 var random = new SecureRandom();
132 random.NextBytes(iv);
133 }
134 catch (Exception ex)
135 {
136 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "IV generation failed", this, GPALObjectType.Cryptography, ex);
137 iv = null;
138 }
139
140 _iv = iv; // Store for state reuse
141 return this;
142 }
143
144 public IAllowCryptoConfig Encrypt(out byte[] encryptedData)
145 {
146 encryptedData = null;
147 if (_key == null)
148 {
149 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot encrypt: missing key", this, GPALObjectType.Cryptography);
150 return this;
151 }
152 if (_inputData == null)
153 {
154 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot encrypt: missing input data", this, GPALObjectType.Cryptography);
155 return this;
156 }
157 if (_mode != CryptoMode.ECB && _iv == null)
158 {
159 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"IV is required for mode [{_mode}], defaulting to ECB", this, GPALObjectType.Cryptography);
160 _mode = CryptoMode.ECB;
161 }
162
163 GPAL.PublishSimpleEvent(GPALEventType.INFO,
164 $"Encrypting with algorithm [{_algorithm}], mode [{_mode}], padding [{_padding}]",
165 this, GPALObjectType.Cryptography);
166
167 try
168 {
169 encryptedData = PerformEncryption();
170 }
171 catch (Exception ex)
172 {
173 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Encryption failed", this, GPALObjectType.Cryptography, ex);
174 encryptedData = null;
175 }
176 return this;
177 }
178
179 public IAllowCryptoConfig Decrypt(out string decryptedValue)
180 {
181 decryptedValue = null;
182 if (_key == null)
183 {
184 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot decrypt: missing key", this, GPALObjectType.Cryptography);
185 return this;
186 }
187 if (_inputData == null)
188 {
189 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot decrypt: missing input data", this, GPALObjectType.Cryptography);
190 return this;
191 }
192 if (_mode != CryptoMode.ECB && _iv == null)
193 {
194 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"IV is required for mode [{_mode}], defaulting to ECB", this, GPALObjectType.Cryptography);
195 _mode = CryptoMode.ECB;
196 }
197
198 GPAL.PublishSimpleEvent(GPALEventType.INFO,
199 $"Decrypting with algorithm [{_algorithm}], mode [{_mode}], padding [{_padding}]",
200 this, GPALObjectType.Cryptography);
201
202 try
203 {
204 byte[] decryptedBytes = PerformDecryption();
205 decryptedValue = System.Text.Encoding.UTF8.GetString(decryptedBytes);
206 }
207 catch (Exception ex)
208 {
209 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Decryption failed", this, GPALObjectType.Cryptography, ex);
210 decryptedValue = null;
211 }
212 return this;
213 }
214
215 public IAllowCryptoConfig Sign(out byte[] signature)
216 {
217 signature = null;
218 if (_key == null)
219 {
220 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot sign: missing key", this, GPALObjectType.Cryptography);
221 return this;
222 }
223 if (_inputData == null)
224 {
225 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot sign: missing input data", this, GPALObjectType.Cryptography);
226 return this;
227 }
228
229 GPAL.PublishSimpleEvent(GPALEventType.INFO,
230 $"Signing with algorithm [{_algorithm}]",
231 this, GPALObjectType.Cryptography);
232
233 try
234 {
235 signature = PerformSigning();
236 }
237 catch (Exception ex)
238 {
239 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Signing failed", this, GPALObjectType.Cryptography, ex);
240 signature = null;
241 }
242 return this;
243 }
244
245 public IAllowCryptoConfig Verify(out bool isValid)
246 {
247 isValid = false;
248 if (_key == null)
249 {
250 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot verify: missing key", this, GPALObjectType.Cryptography);
251 return this;
252 }
253 if (_inputData == null)
254 {
255 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot verify: missing input data", this, GPALObjectType.Cryptography);
256 return this;
257 }
258 if (_signature == null)
259 {
260 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot verify: missing signature", this, GPALObjectType.Cryptography);
261 return this;
262 }
263
264 GPAL.PublishSimpleEvent(GPALEventType.INFO,
265 $"Verifying with algorithm [{_algorithm}]",
266 this, GPALObjectType.Cryptography);
267
268 try
269 {
270 isValid = PerformVerification();
271 }
272 catch (Exception ex)
273 {
274 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Verification failed", this, GPALObjectType.Cryptography, ex);
275 isValid = false;
276 }
277 return this;
278 }
279
280 public IAllowCryptoConfig Hash(out byte[] hash)
281 {
282 hash = null;
283 if (_inputData == null)
284 {
285 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot hash: missing input data", this, GPALObjectType.Cryptography);
286 return this;
287 }
288
289 GPAL.PublishSimpleEvent(GPALEventType.INFO,
290 $"Hashing with algorithm [{_algorithm}]" + (_salt != null ? $", salt length [{_salt.Length}]" : ""),
291 this, GPALObjectType.Cryptography);
292
293 try
294 {
295 hash = PerformHashing();
296 }
297 catch (Exception ex)
298 {
299 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Hashing failed", this, GPALObjectType.Cryptography, ex);
300 hash = null;
301 }
302 return this;
303 }
304
305 public IAllowCryptoConfig GenerateKey(out byte[] key)
306 {
307 key = null;
308 if (_algorithm == CryptoAlgorithm.PBKDF2 && (_salt == null || _inputData == null))
309 {
310 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot generate key with PBKDF2: missing salt or input data", this, GPALObjectType.Cryptography);
311 return this;
312 }
313
314 GPAL.PublishSimpleEvent(GPALEventType.INFO,
315 $"Generating key with algorithm [{_algorithm}]" + (_algorithm == CryptoAlgorithm.PBKDF2 ? $", iteration count [{_iterationCount}]" : ""),
316 this, GPALObjectType.Cryptography);
317
318 try
319 {
320 key = PerformKeyGeneration();
321 }
322 catch (Exception ex)
323 {
324 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Key generation failed", this, GPALObjectType.Cryptography, ex);
325 key = null;
326 }
327 _key = key;
328 return this;
329 }
330
331 private byte[] PerformEncryption()
332 {
333 if (_algorithm != CryptoAlgorithm.AES)
334 {
335 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to AES", this, GPALObjectType.Cryptography);
336 _algorithm = CryptoAlgorithm.AES;
337 }
338
339 if (_mode == CryptoMode.GCM)
340 {
341 IAeadBlockCipher cipher = new GcmBlockCipher(new AesEngine());
342 ICipherParameters parameters = new AeadParameters(new KeyParameter(_key), 128, _iv, null);
343 cipher.Init(true, parameters);
344 byte[] output = new byte[cipher.GetOutputSize(_inputData.Length)];
345 int len = cipher.ProcessBytes(_inputData, 0, _inputData.Length, output, 0);
346 cipher.DoFinal(output, len);
347 return output;
348 }
349 else
350 {
351 IBufferedCipher cipher = _mode == CryptoMode.CBC
352 ? new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine()), new Pkcs7Padding())
353 : new PaddedBufferedBlockCipher(new AesEngine(), new Pkcs7Padding()); // ECB
354
355 ICipherParameters parameters;
356 if (_iv != null)
357 {
358 parameters = new ParametersWithIV(new KeyParameter(_key), _iv);
359 }
360 else
361 {
362 parameters = new KeyParameter(_key);
363 }
364
365 cipher.Init(true, parameters);
366 return cipher.DoFinal(_inputData);
367 }
368 }
369
370 private byte[] PerformDecryption()
371 {
372 if (_algorithm != CryptoAlgorithm.AES)
373 {
374 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to AES", this, GPALObjectType.Cryptography);
375 _algorithm = CryptoAlgorithm.AES;
376 }
377
378 if (_mode == CryptoMode.GCM)
379 {
380 IAeadBlockCipher cipher = new GcmBlockCipher(new AesEngine());
381 ICipherParameters parameters = new AeadParameters(new KeyParameter(_key), 128, _iv, null);
382 cipher.Init(false, parameters);
383 byte[] output = new byte[cipher.GetOutputSize(_inputData.Length)];
384 int len = cipher.ProcessBytes(_inputData, 0, _inputData.Length, output, 0);
385 cipher.DoFinal(output, len);
386 return output;
387 }
388 else
389 {
390 IBufferedCipher cipher = _mode == CryptoMode.CBC
391 ? new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine()), new Pkcs7Padding())
392 : new PaddedBufferedBlockCipher(new AesEngine(), new Pkcs7Padding()); // ECB
393
394 ICipherParameters parameters;
395 if (_iv != null)
396 {
397 parameters = new ParametersWithIV(new KeyParameter(_key), _iv);
398 }
399 else
400 {
401 parameters = new KeyParameter(_key);
402 }
403
404 cipher.Init(false, parameters);
405 return cipher.DoFinal(_inputData);
406 }
407 }
408
409 private byte[] PerformSigning()
410 {
411 if (_algorithm != CryptoAlgorithm.SHA256withRSA)
412 {
413 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to SHA256withRSA", this, GPALObjectType.Cryptography);
414 _algorithm = CryptoAlgorithm.SHA256withRSA;
415 }
416
417 AsymmetricKeyParameter keyParam;
418 try
419 {
420 var keyInfo = PrivateKeyInfo.GetInstance(_key);
421 var rsaParams = RsaPrivateKeyStructure.GetInstance(keyInfo.ParsePrivateKey());
422 keyParam = new RsaKeyParameters(true, rsaParams.Modulus, rsaParams.PrivateExponent);
423 }
424 catch (Exception ex)
425 {
426 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Invalid RSA private key format", this, GPALObjectType.Cryptography, ex);
427 return null;
428 }
429
430 ISigner signer = SignerUtilities.GetSigner("SHA256withRSA");
431 signer.Init(true, keyParam);
432 signer.BlockUpdate(_inputData, 0, _inputData.Length);
433 return signer.GenerateSignature();
434 }
435
436 private bool PerformVerification()
437 {
438 if (_algorithm != CryptoAlgorithm.SHA256withRSA)
439 {
440 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to SHA256withRSA", this, GPALObjectType.Cryptography);
441 _algorithm = CryptoAlgorithm.SHA256withRSA;
442 }
443
444 AsymmetricKeyParameter keyParam;
445 try
446 {
447 var keyInfo = SubjectPublicKeyInfo.GetInstance(_key);
448 var rsaParams = RsaPublicKeyStructure.GetInstance(keyInfo.ParsePublicKey());
449 keyParam = new RsaKeyParameters(false, rsaParams.Modulus, rsaParams.PublicExponent);
450 }
451 catch (Exception ex)
452 {
453 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Invalid RSA public key format", this, GPALObjectType.Cryptography, ex);
454 return false;
455 }
456
457 ISigner signer = SignerUtilities.GetSigner("SHA256withRSA");
458 signer.Init(false, keyParam);
459 signer.BlockUpdate(_inputData, 0, _inputData.Length);
460 return signer.VerifySignature(_signature); // Assumes _inputData is the signature
461 }
462
463 private byte[] PerformHashing()
464 {
465 if (_algorithm != CryptoAlgorithm.SHA256)
466 {
467 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to SHA256", this, GPALObjectType.Cryptography);
468 _algorithm = CryptoAlgorithm.SHA256;
469 }
470
471 IDigest digest = DigestUtilities.GetDigest("SHA256");
472 if (_salt != null)
473 {
474 digest.BlockUpdate(_salt, 0, _salt.Length);
475 }
476 digest.BlockUpdate(_inputData, 0, _inputData.Length);
477 byte[] hash = new byte[digest.GetDigestSize()];
478 digest.DoFinal(hash, 0);
479 return hash;
480 }
481
482 private byte[] PerformKeyGeneration()
483 {
484 if (_algorithm == CryptoAlgorithm.PBKDF2)
485 {
486 if (_salt == null || _inputData == null)
487 {
488 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Cannot generate key with PBKDF2: missing salt or input data", this, GPALObjectType.Cryptography);
489 return null;
490 }
491
492 Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
493 gen.Init(_inputData, _salt, _iterationCount);
494 return ((KeyParameter)gen.GenerateDerivedParameters("AES", 256)).GetKey();
495 }
496
497 if (_algorithm != CryptoAlgorithm.AES)
498 {
499 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Algorithm [{_algorithm}] not supported, defaulting to AES", this, GPALObjectType.Cryptography);
500 _algorithm = CryptoAlgorithm.AES;
501 }
502
503 var generator = GeneratorUtilities.GetKeyGenerator("AES");
504 return generator.GenerateKey();
505 }
506 #region Helpers
507 public static (byte[], byte[]) GenerateRsaKeyPair()
508 {
509 try
510 {
511 var keyPairGen = GeneratorUtilities.GetKeyPairGenerator("RSA");
512 keyPairGen.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
513 var keyPair = keyPairGen.GenerateKeyPair();
514
515 // Private key (PKCS#8)
516 var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
517 byte[] privateKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();
518
519 // Public key (X.509)
520 var publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
521 byte[] publicKey = publicKeyInfo.ToAsn1Object().GetDerEncoded();
522
523 return (privateKey, publicKey);
524 }
525 catch (Exception)
526 {
527 // Since not part of fluent interface, return default values instead of throwing
528 return (null, null);
529 }
530 }
531 #endregion Helpers
532 public ICryptography ToGPALObject()
533 {
534 return this;
535 }
536 }
537}
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406