();
private readonly SymmetricKeyAlgorithmTag defAlgorithm;
private readonly SecureRandom rand;
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm)
{
this.defAlgorithm = encAlgorithm;
this.rand = CryptoServicesRegistrar.GetSecureRandom();
}
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
bool withIntegrityPacket)
{
this.defAlgorithm = encAlgorithm;
this.withIntegrityPacket = withIntegrityPacket;
this.rand = CryptoServicesRegistrar.GetSecureRandom();
}
/// Existing SecureRandom constructor.
/// The symmetric algorithm to use.
/// Source of randomness.
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
SecureRandom random)
{
if (random == null)
throw new ArgumentNullException(nameof(random));
this.defAlgorithm = encAlgorithm;
this.rand = random;
}
/// Creates a cipher stream which will have an integrity packet associated with it.
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
bool withIntegrityPacket,
SecureRandom random)
{
if (random == null)
throw new ArgumentNullException(nameof(random));
this.defAlgorithm = encAlgorithm;
this.rand = random;
this.withIntegrityPacket = withIntegrityPacket;
}
/// Base constructor.
/// The symmetric algorithm to use.
/// Source of randomness.
/// PGP 2.6.x compatibility required.
public PgpEncryptedDataGenerator(
SymmetricKeyAlgorithmTag encAlgorithm,
SecureRandom random,
bool oldFormat)
{
if (random == null)
throw new ArgumentNullException(nameof(random));
this.defAlgorithm = encAlgorithm;
this.rand = random;
this.oldFormat = oldFormat;
}
/// Add a PBE encryption method to the encrypted object.
///
/// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is
/// the historical behaviour of the library (1.7 and earlier).
///
public void AddMethod(char[] passPhrase, HashAlgorithmTag s2kDigest)
{
DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, s2kDigest);
}
/// Add a PBE encryption method to the encrypted object.
///
/// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes).
///
public void AddMethodUtf8(char[] passPhrase, HashAlgorithmTag s2kDigest)
{
DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, s2kDigest);
}
/// Add a PBE encryption method to the encrypted object.
///
/// Allows the caller to handle the encoding of the passphrase to bytes.
///
public void AddMethodRaw(byte[] rawPassPhrase, HashAlgorithmTag s2kDigest)
{
DoAddMethod(rawPassPhrase, false, s2kDigest);
}
internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgorithmTag s2kDigest)
{
S2k s2k = PgpUtilities.GenerateS2k(s2kDigest, 0x60, rand);
methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase)));
}
/// Add a public key encrypted session key to the encrypted object.
public void AddMethod(PgpPublicKey key)
{
AddMethod(key, true);
}
public void AddMethod(PgpPublicKey key, bool sessionKeyObfuscation)
{
if (!key.IsEncryptionKey)
{
throw new ArgumentException("passed in key not an encryption key!");
}
methods.Add(new PubMethod(key, sessionKeyObfuscation));
}
private void AddCheckSum(
byte[] sessionInfo)
{
Debug.Assert(sessionInfo != null);
Debug.Assert(sessionInfo.Length >= 3);
int check = 0;
for (int i = 1; i < sessionInfo.Length - 2; i++)
{
check += sessionInfo[i];
}
sessionInfo[sessionInfo.Length - 2] = (byte)(check >> 8);
sessionInfo[sessionInfo.Length - 1] = (byte)(check);
}
private byte[] CreateSessionInfo(
SymmetricKeyAlgorithmTag algorithm,
KeyParameter key)
{
byte[] keyBytes = key.GetKey();
byte[] sessionInfo = new byte[keyBytes.Length + 3];
sessionInfo[0] = (byte) algorithm;
keyBytes.CopyTo(sessionInfo, 1);
AddCheckSum(sessionInfo);
return sessionInfo;
}
///
///
/// If buffer is non null stream assumed to be partial, otherwise the length will be used
/// to output a fixed length packet.
///
///
/// The stream created can be closed off by either calling Close()
/// on the stream or Close() on the generator. Closing the returned
/// stream does not close off the Stream parameter outStr.
///
///
private Stream Open(
Stream outStr,
long length,
byte[] buffer)
{
if (cOut != null)
throw new InvalidOperationException("generator already in open state");
if (methods.Count == 0)
throw new InvalidOperationException("No encryption methods specified");
if (outStr == null)
throw new ArgumentNullException("outStr");
pOut = new BcpgOutputStream(outStr);
KeyParameter key;
if (methods.Count == 1)
{
if (methods[0] is PbeMethod pbeMethod)
{
key = pbeMethod.GetKey();
}
else if (methods[0] is PubMethod pubMethod)
{
key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
try
{
pubMethod.AddSessionInfo(sessionInfo, rand);
}
catch (Exception e)
{
throw new PgpException("exception encrypting session key", e);
}
}
else
{
throw new InvalidOperationException();
}
pOut.WritePacket(methods[0]);
}
else // multiple methods
{
key = PgpUtilities.MakeRandomKey(defAlgorithm, rand);
byte[] sessionInfo = CreateSessionInfo(defAlgorithm, key);
foreach (EncMethod m in methods)
{
try
{
m.AddSessionInfo(sessionInfo, rand);
}
catch (Exception e)
{
throw new PgpException("exception encrypting session key", e);
}
pOut.WritePacket(m);
}
}
string cName = PgpUtilities.GetSymmetricCipherName(defAlgorithm);
if (cName == null)
throw new PgpException("null cipher specified");
try
{
if (withIntegrityPacket)
{
cName += "/CFB/NoPadding";
}
else
{
cName += "/OpenPGPCFB/NoPadding";
}
c = CipherUtilities.GetCipher(cName);
// TODO Confirm the IV should be all zero bytes (not inLineIv - see below)
byte[] iv = new byte[c.GetBlockSize()];
c.Init(true, new ParametersWithRandom(new ParametersWithIV(key, iv), rand));
if (buffer == null)
{
//
// we have to Add block size + 2 for the Generated IV and + 1 + 22 if integrity protected
//
if (withIntegrityPacket)
{
pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, length + c.GetBlockSize() + 2 + 1 + 22);
pOut.WriteByte(1); // version number
}
else
{
pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, length + c.GetBlockSize() + 2, oldFormat);
}
}
else
{
if (withIntegrityPacket)
{
pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricEncryptedIntegrityProtected, buffer);
pOut.WriteByte(1); // version number
}
else
{
pOut = new BcpgOutputStream(outStr, PacketTag.SymmetricKeyEncrypted, buffer);
}
}
int blockSize = c.GetBlockSize();
byte[] inLineIv = new byte[blockSize + 2];
rand.NextBytes(inLineIv, 0, blockSize);
Array.Copy(inLineIv, inLineIv.Length - 4, inLineIv, inLineIv.Length - 2, 2);
Stream myOut = cOut = new CipherStream(pOut, null, c);
if (withIntegrityPacket)
{
IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
myOut = digestOut = new DigestStream(myOut, null, digest);
}
myOut.Write(inLineIv, 0, inLineIv.Length);
return new WrappedGeneratorStream(this, myOut);
}
catch (Exception e)
{
throw new PgpException("Exception creating cipher", e);
}
}
///
///
/// Return an output stream which will encrypt the data as it is written to it.
///
///
/// The stream created can be closed off by either calling Close()
/// on the stream or Close() on the generator. Closing the returned
/// stream does not close off the Stream parameter outStr.
///
///
public Stream Open(
Stream outStr,
long length)
{
return Open(outStr, length, null);
}
///
///
/// Return an output stream which will encrypt the data as it is written to it.
/// The stream will be written out in chunks according to the size of the passed in buffer.
///
///
/// The stream created can be closed off by either calling Close()
/// on the stream or Close() on the generator. Closing the returned
/// stream does not close off the Stream parameter outStr.
///
///
/// Note: if the buffer is not a power of 2 in length only the largest power of 2
/// bytes worth of the buffer will be used.
///
///
public Stream Open(
Stream outStr,
byte[] buffer)
{
return Open(outStr, 0, buffer);
}
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (cOut != null)
{
// TODO Should this all be under the try/catch block?
if (digestOut != null)
{
//
// hand code a mod detection packet
//
BcpgOutputStream bOut = new BcpgOutputStream(
digestOut, PacketTag.ModificationDetectionCode, 20);
bOut.Flush();
digestOut.Flush();
// TODO
byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest);
cOut.Write(dig, 0, dig.Length);
}
cOut.Flush();
try
{
pOut.Write(c.DoFinal());
pOut.Finish();
}
catch (Exception e)
{
throw new IOException(e.Message, e);
}
cOut = null;
pOut = null;
}
}
}
#endregion
}
}