Following is the perfect C# solution that has the same outputs as EncryptByPassPhrase and DecryptByPassPhrase in SQL functions.
Program.cs
using SQLServerCrypto;
static void Main()
{
// EncryptByPassPhrase
var passphrase = "test1234";
var encrypted = SQLServerCryptoMethod.EncryptByPassPhrase(@passphrase, "Hello world.");
System.Console.WriteLine(encrypted.ToString().ToUpper());
// DecryptByPassPhrase
var ciphertext = "0x0100000038C94F7223E0BA2F772B611857F9D45DAF781607CC77F4A856CF08CC2DB9DF14A0593259CB3A4A2BFEDB485C002CA04B6A98BEB1B47EB107";
var password = "test1234";
var decrypted = SQLServerCryptoMethod.DecryptByPassPhraseWithoutVerification(password, ciphertext);
Console.WriteLine(decrypted);
}
SQLServerCryptoMethod.cs
using System;
using System.Linq;
namespace SQLServerCrypto
{
public static class SQLServerCryptoMethod
{
// SQL Server: https://learn.microsoft.com/en-us/sql/t-sql/functions/encryptbypassphrase-transact-sql
public static HexString EncryptByPassPhrase(string passphrase, string cleartext, int add_authenticator, string authenticator)
=> EncryptByPassPhrase(passphrase, cleartext, add_authenticator, authenticator, SQLServerCryptoVersion.V1);
public static HexString EncryptByPassPhrase(string passphrase, string cleartext, SQLServerCryptoVersion sqlServerCryptoVersion)
=> EncryptByPassPhrase(passphrase, cleartext, 0, string.Empty, sqlServerCryptoVersion);
public static HexString EncryptByPassPhrase(string passphrase, string cleartext)
=> EncryptByPassPhrase(passphrase, cleartext, 0, string.Empty, SQLServerCryptoVersion.V1);
public static HexString EncryptByPassPhrase(string passphrase, string cleartext, int add_authenticator, string authenticator, SQLServerCryptoVersion sqlServerCryptoVersion)
{
var sqlServerCryptoAlgorithm = new SQLServerCryptoAlgorithm(sqlServerCryptoVersion);
sqlServerCryptoAlgorithm.SetKeyFromPassPhrase(passphrase);
byte[] header = new SQLServerCryptoHeader() {
Version = sqlServerCryptoVersion,
InitializationVector = sqlServerCryptoAlgorithm.Symmetric.IV
};
var sqlServerCryptoMessage = new SQLServerCryptoMessage()
{
AddAuthenticator = add_authenticator > 0,
Authenticator = authenticator
};
sqlServerCryptoMessage.CreateFromClearText(cleartext);
byte[] message = sqlServerCryptoMessage;
var encryptedMessage = sqlServerCryptoAlgorithm.Symmetric
.CreateEncryptor()
.TransformFinalBlock(message, 0, message.Length);
return new HexString(header.Concat(encryptedMessage).ToArray());
}
// SQL Server: https://learn.microsoft.com/en-us/sql/t-sql/functions/decryptbypassphrase-transact-sql
public static string DecryptByPassPhrase(string passphrase, string ciphertext)
=> DecryptByPassPhrase(passphrase, new HexString(ciphertext), 0, string.Empty, true);
public static string DecryptByPassPhrase(string passphrase, string ciphertext, int add_authenticator, string authenticator)
=> DecryptByPassPhrase(passphrase, new HexString(ciphertext), add_authenticator, authenticator, true);
public static string DecryptByPassPhraseWithoutVerification(string passphrase, string ciphertext)
=> DecryptByPassPhrase(passphrase, new HexString(ciphertext), 0, string.Empty, false);
public static string DecryptByPassPhrase(string passphrase, HexString ciphertext, int add_authenticator, string authenticator, bool verify)
{
byte[] ciphertextBytes = ciphertext.ToByteArray();
var version = (SQLServerCryptoVersion)ciphertextBytes[0];
var sqlServerCryptoAlgorithm = new SQLServerCryptoAlgorithm(version);
sqlServerCryptoAlgorithm.SetKeyFromPassPhrase(passphrase);
var versionAndReservedSize = 4;
var ivSize = sqlServerCryptoAlgorithm.KeySize / 2;
var header = new SQLServerCryptoHeader
{
Version = version,
InitializationVector = ciphertextBytes.Skip(versionAndReservedSize).Take(ivSize).ToArray()
};
sqlServerCryptoAlgorithm.Symmetric.IV = header.InitializationVector;
var encryptedMessage = ciphertextBytes.Skip(versionAndReservedSize+ivSize).ToArray();
var decryptedMessage = sqlServerCryptoAlgorithm.Symmetric
.CreateDecryptor()
.TransformFinalBlock(encryptedMessage, 0, encryptedMessage.Length);
// Message
var sqlServerCryptoMessage = new SQLServerCryptoMessage()
{
AddAuthenticator = add_authenticator > 0,
Authenticator = authenticator
};
sqlServerCryptoMessage.CreateFromDecryptedMessage(decryptedMessage, verify);
return ByteArray2String(sqlServerCryptoMessage.MessageBytes);
}
private static string ByteArray2String(byte[] array)
=> array.Aggregate(string.Empty, (a, b) => a + Convert.ToChar(b));
}
}
SQLServerCryptoMessage.cs
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Security.Cryptography;
namespace SQLServerCrypto
{
public class SQLServerCryptoMessage
{
private const uint MAGIC_NUMBER = 0xbaad_f00d;
public uint MagicNumber { get; private set; }
public ushort IntegrityBytesLength { get; private set; }
public ushort PlainTextLength { get; private set; }
public byte[] IntegrityBytes;
public byte[] MessageBytes;
public bool AddAuthenticator = false;
private string _authenticator;
public string Authenticator
{
get => _authenticator;
set =>
_authenticator = value.Length > 128 ?
throw new ArgumentOutOfRangeException("The size of the authenticator string should not exceed 128 bytes.")
: value;
}
public SQLServerCryptoMessage() => MagicNumber = MAGIC_NUMBER;
public void CreateFromClearText(string cleartext)
{
MessageBytes = Encoding.ASCII.GetBytes(cleartext);
if (MessageBytes.Length > 8000)
throw new ArgumentOutOfRangeException("The size of the cleartext string should not exceed 8000 bytes.");
MagicNumber = MAGIC_NUMBER;
IntegrityBytesLength = 0;
PlainTextLength = (ushort)MessageBytes.Length;
if (AddAuthenticator)
{
var integrityMessage = MessageBytes.Concat(Encoding.ASCII.GetBytes(Authenticator)).ToArray();
IntegrityBytes = SHA1.Create().ComputeHash(integrityMessage);
IntegrityBytesLength = (ushort)IntegrityBytes.Length;
}
}
public void CreateFromDecryptedMessage(byte[] decryptedMessage, bool verify = true)
{
MagicNumber = BitConverter.ToUInt32(decryptedMessage, 0);
IntegrityBytesLength = BitConverter.ToUInt16(decryptedMessage, 4);
PlainTextLength = BitConverter.ToUInt16(decryptedMessage, 6);
var messageWithoutHeader = decryptedMessage.Skip(8);
if (IntegrityBytesLength > 0 || IntegrityBytesLength < 0xffff)
IntegrityBytes = messageWithoutHeader.Take(IntegrityBytesLength).ToArray();
if (IntegrityBytesLength != 0xffff)
MessageBytes = messageWithoutHeader.Skip(IntegrityBytesLength).ToArray();
else
MessageBytes = messageWithoutHeader.ToArray();
if (verify)
VerifyMessage();
}
private void VerifyMessage()
{
if (MagicNumber != MAGIC_NUMBER)
throw new Exception("Message integrity error. Magic numbers are different.");
var integrityMessage = MessageBytes.Concat(Encoding.ASCII.GetBytes(Authenticator)).ToArray();
var hash = SHA1.Create().ComputeHash(integrityMessage);
if (IntegrityBytes.Length > 0 && !hash.SequenceEqual(IntegrityBytes))
throw new Exception("Message integrity error. Invalid authenticator.");
if (PlainTextLength != MessageBytes.Length)
throw new Exception("Message integrity error. Invalid message length.");
}
public static implicit operator byte[](SQLServerCryptoMessage sqlServerCryptoMessage) => sqlServerCryptoMessage.ToByteArray();
public byte[] ToByteArray()
{
byte[] result;
using (var memoryStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(memoryStream))
{
binaryWriter.Write(MagicNumber);
binaryWriter.Write(IntegrityBytesLength);
binaryWriter.Write(PlainTextLength);
if (IntegrityBytes != null)
binaryWriter.Write(IntegrityBytes);
if (MessageBytes != null)
binaryWriter.Write(MessageBytes);
}
result = memoryStream.ToArray();
}
return result;
}
}
}
SQLServerCryptoAlghorithm.cs
using System;
using System.Text;
using System.Linq;
using System.Security.Cryptography;
namespace SQLServerCrypto
{
public class SQLServerCryptoAlgorithm
{
public readonly SQLServerCryptoVersion Version;
public readonly HashAlgorithm Hash;
public readonly SymmetricAlgorithm Symmetric;
public readonly int KeySize;
public SQLServerCryptoAlgorithm(SQLServerCryptoVersion sqlCryptoVersion)
{
Version = sqlCryptoVersion;
switch (Version)
{
case SQLServerCryptoVersion.V1:
Hash = SHA1.Create();
Symmetric = TripleDES.Create();
KeySize = 16;
break;
case SQLServerCryptoVersion.V2:
Hash = SHA256.Create();
Symmetric = Aes.Create();
KeySize = 32;
break;
default:
throw new Exception("Unsupported SQLServerCryptoVersion");
}
Symmetric.Padding = PaddingMode.PKCS7;
Symmetric.Mode = CipherMode.CBC;
}
public void SetKeyFromPassPhrase(string passphrase)
=> Symmetric.Key = Hash
.ComputeHash(Encoding.Unicode.GetBytes(passphrase))
.Take(KeySize)
.ToArray();
}
}
SQLServerCryptoHeader.cs
using System.Collections.Generic;
namespace SQLServerCrypto
{
public class SQLServerCryptoHeader
{
public SQLServerCryptoVersion Version = SQLServerCryptoVersion.V1;
public byte[] Reserved = { 0, 0, 0 };
public byte[] InitializationVector = { };
public SQLServerCryptoHeader(SQLServerCryptoVersion sqlServerCryptoVersion = SQLServerCryptoVersion.V1)
=> Version = sqlServerCryptoVersion;
public static implicit operator byte[] (SQLServerCryptoHeader sqlServerCryptoHeader) => sqlServerCryptoHeader.ToByteArray();
public byte[] ToByteArray()
{
var result = new List<byte>();
result.Add((byte)Version);
result.AddRange(Reserved);
result.AddRange(InitializationVector);
return result.ToArray();
}
}
}
SQLServerCryptoVersion.cs
namespace SQLServerCrypto
{
public enum SQLServerCryptoVersion
{
///<summary>
/// TripleDES/SHA1
/// </summary>
V1 = 0x01,
/// <summary>
/// AES256/SHA256
/// </summary>
V2 = 0x02
}
}
HexString.cs
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace SQLServerCrypto
{
public sealed class HexString
{
private readonly byte[] _byteArray;
private readonly Regex RegExValidation = new Regex("^[0-9a-fA-F]+$", RegexOptions.Compiled);
private const string PREFIX = "0x";
public HexString(string hexString)
{
if (string.IsNullOrEmpty(hexString))
throw new ArgumentException("Input string is null or empty.");
hexString = RemovePrefix(hexString);
if (hexString.Length % 2 != 0)
throw new ArgumentException("Invalid number of hexcharacters.");
if (!RegExValidation.IsMatch(hexString))
throw new ArgumentException("Input string does not contain hexadecimal characters.");
_byteArray = HexStringToByteArray(hexString);
}
public HexString(byte[] byteArray)
{
if (byteArray == null)
throw new ArgumentException("Input array is null.");
if (byteArray.Length == 0)
throw new ArgumentException("Input array is empty.");
_byteArray = byteArray;
}
public string ValueWithoutPrefix => ByteArrayToHexString(_byteArray);
public string ValueWithPrefix => PREFIX + ValueWithoutPrefix;
public override string ToString() => ValueWithPrefix;
public byte[] ToByteArray() => _byteArray;
public static implicit operator string(HexString hexString) => hexString.ToString();
public static implicit operator byte[](HexString hexString) => hexString._byteArray;
private static string RemovePrefix(string input) => input.StartsWith(PREFIX) ? input.Remove(0, 2) : input;
// https://mcmap.net/q/93055/-how-can-i-convert-a-hex-string-to-a-byte-array-duplicate#321404
// Looks nice but could be faster.
private static byte[] HexStringToByteArray(string hexString) => Enumerable.Range(0, hexString.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hexString.Substring(x, 2), 16))
.ToArray();
private static string ByteArrayToHexString(byte[] byteArray) =>
BitConverter.ToString(byteArray).Replace("-", string.Empty).ToLower();
}
}
Please refer to the following link for the full source project.
https://github.com/persec10000/Test_Invoice2QrCodeWithCryptedLink