private static bool VerifyChecksum( S2kUsageTag usageTag, ReadOnlySpan <byte> data, out int checksumLength) { if (usageTag == S2kUsageTag.Sha1) { using var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); sha1.AppendData(data.Slice(0, data.Length - sha1.HashLengthInBytes)); checksumLength = sha1.HashLengthInBytes; return(sha1.GetHashAndReset().AsSpan().SequenceEqual(data.Slice(data.Length - sha1.HashLengthInBytes))); } else if (usageTag == S2kUsageTag.Checksum) { int checksum = 0; for (int i = 0; i < data.Length - 2; i++) { checksum += data[i]; } checksumLength = 2; return(data[data.Length - 2] == (byte)(checksum >> 8) && data[data.Length - 1] == (byte)checksum); } else // None { checksumLength = 0; return(true); } }
public static void DecryptSecretKey(ReadOnlySpan <byte> password, ReadOnlySpan <byte> source, Span <byte> destination, out int bytesWritten, int version = 4) { S2kUsageTag usageTag = (S2kUsageTag)source[0]; PgpSymmetricKeyAlgorithm encryptionAlgorithm; var salt = new ReadOnlySpan <byte>(); long iterationCount = 0; PgpHashAlgorithm hashAlgorithm; if (usageTag == S2kUsageTag.Checksum || usageTag == S2kUsageTag.Sha1 /* || usageTag == S2kUsageTag.Aead */) { encryptionAlgorithm = (PgpSymmetricKeyAlgorithm)source[1]; byte s2kType = source[2]; hashAlgorithm = (PgpHashAlgorithm)source[3]; source = source.Slice(4); if (s2kType > 0 && s2kType <= 3) { salt = source.Slice(0, 8); source = source.Slice(8); if (s2kType == 3) { iterationCount = (16 + (source[0] & 15)) << ((source[0] >> 4) + 6); source = source.Slice(1); } } else if (s2kType == 101) // GNU private { throw new NotImplementedException(); } else { throw new CryptographicException(); // Unknown S2K type } } else if (usageTag == S2kUsageTag.None) { // No encryption bytesWritten = source.Slice(1).TryCopyTo(destination) ? source.Length : 0; return; } else { // No salt, no iterations, MD5 hash encryptionAlgorithm = (PgpSymmetricKeyAlgorithm)usageTag; hashAlgorithm = PgpHashAlgorithm.MD5; usageTag = S2kUsageTag.Checksum; source = source.Slice(1); } int keySizeInBytes = (PgpUtilities.GetKeySize(encryptionAlgorithm) + 7) / 8; Span <byte> keyBytes = stackalloc byte[keySizeInBytes]; try { MakeKey(password, hashAlgorithm, salt, iterationCount, keyBytes); using var c = PgpUtilities.GetSymmetricAlgorithm(encryptionAlgorithm); // Read the IV c.IV = source.Slice(0, c.BlockSize / 8).ToArray(); c.Key = keyBytes.ToArray(); source = source.Slice(c.BlockSize / 8); if (destination.Length < source.Length) { bytesWritten = 0; return; } // Do the actual decryption bytesWritten = source.Length; if (version == 4) { using var decryptor = new ZeroPaddedCryptoTransform(c.CreateDecryptor()); var data = decryptor.TransformFinalBlock(source.ToArray(), 0, source.Length); data.AsSpan().CopyTo(destination); CryptographicOperations.ZeroMemory(data); } else if (version == 3) { // Version 3 is four RSA parameters encoded as MPIntegers separately var sourceArray = source.ToArray(); int pos = 0; for (int i = 0; i != 4; i++) { int encLen = ((source[pos] << 8) + source[pos + 1] + 7) / 8; destination[pos] = source[pos]; destination[pos + 1] = source[pos + 1]; pos += 2; if (encLen > source.Length - pos) { throw new PgpException("out of range encLen found in encData"); } using var decryptor = new ZeroPaddedCryptoTransform(c.CreateDecryptor()); var data = decryptor.TransformFinalBlock(sourceArray, pos, encLen); data.CopyTo(destination.Slice(pos)); CryptographicOperations.ZeroMemory(data); pos += encLen; if (i != 3) { c.IV = source.Slice(pos - (c.BlockSize / 8), c.BlockSize / 8).ToArray(); } } destination[pos] = source[pos]; destination[pos + 1] = source[pos + 1]; } if (!VerifyChecksum(usageTag, destination.Slice(0, bytesWritten), out int checksumLength)) { throw new CryptographicException(); } bytesWritten -= checksumLength; } finally { CryptographicOperations.ZeroMemory(keyBytes); } }