Created
December 23, 2019 15:29
-
-
Save cwschroeder/fa79ea20e2bbf13ee35f7d8c8b6f2a0e to your computer and use it in GitHub Desktop.
ECC Zeug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Main() | |
{ | |
/* | |
. Seeding the random number generator... ok | |
. Generating key pair... ok (key size: 256 bits) | |
+ Public key: 0478758D742D29533E8ADFC95B7AA6071397473B0D567FEFE8D36C21958DAA22291E2CF4860C3754B39532E27C718FE68CE60E05CCA39F5F09DE29C9857ABC6B56 | |
. Computing message hash... ok | |
+ Hash: 02F4CCF09D1F5740E28864C430F866B46B697646D376E7813D12B873DBE16CC6 | |
. Signing message hash... ok (signature length = 71) | |
+ Signature: 304502204E7D544731E4B9A1343E532012E71B7812B069CB6907A246E759DE6D0AF3055B022100CA8C294F80BC5A84373B09733F164654AE0F559F365FA54C93BE4855BE8B54D8 | |
. Preparing verification context... ok | |
. Verifying signature... ok | |
+ Press Enter to exit this program. | |
*/ | |
for(int i =0;i<10;i++) | |
{ | |
// create ECC key pair for curve secp256r1 | |
(var priv, var pub) = Crypto.CreateEccKeyPair(); | |
// create dummy message that will be signed | |
var plainData = Enumerable.Range(1,100).Select(b=>(byte)b).ToArray(); | |
// create signature for message | |
var signature = Crypto.CreateSignature(plainData, priv); | |
// verify signature | |
var res = Crypto.VerifySignature(pub.Q.GetEncoded(), plainData, signature, false); | |
if (res != true){ | |
Console.WriteLine("ERR"); | |
} | |
if(i%100 == 0){ | |
Thread.Sleep(100); | |
} | |
} | |
var testpkBytes = "0478758D742D29533E8ADFC95B7AA6071397473B0D567FEFE8D36C21958DAA22291E2CF4860C3754B39532E27C718FE68CE60E05CCA39F5F09DE29C9857ABC6B56".FromHexStr(); | |
var hash ="02F4CCF09D1F5740E28864C430F866B46B697646D376E7813D12B873DBE16CC6".FromHexStr(); | |
var sig ="304502204E7D544731E4B9A1343E532012E71B7812B069CB6907A246E759DE6D0AF3055B022100CA8C294F80BC5A84373B09733F164654AE0F559F365FA54C93BE4855BE8B54D8".FromHexStr(); | |
var testEcParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1"); | |
var testpkPoint = testEcParams.Curve.DecodePoint(testpkBytes.ToArray()); | |
Asn1InputStream bIn = new Asn1InputStream(new MemoryStream(sig)); | |
DerSequence seq = bIn.ReadObject() as DerSequence; | |
var resutl = Crypto.VerifySignature(testpkBytes,hash,sig); | |
var rnd = new Random(); | |
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp192r1"); | |
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, | |
ecParams.G, ecParams.N, ecParams.H, | |
ecParams.GetSeed()); | |
ECKeyGenerationParameters keyGenParams = | |
new ECKeyGenerationParameters(domainParameters, new SecureRandom()); | |
AsymmetricCipherKeyPair keyPair; | |
ECKeyPairGenerator generator = new ECKeyPairGenerator(); | |
generator.Init(keyGenParams); | |
keyPair = generator.GenerateKeyPair(); | |
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private; | |
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public; | |
CreateAsn1Key(keyPair); | |
var bytes = publicKey.Q.GetEncoded(true).ToList(); | |
var privateKeyBytes = privateKey.D.ToByteArray(); | |
var recoveredPrivateKey = new ECPrivateKeyParameters(new BigInteger(privateKeyBytes), domainParameters); | |
// import public key from compressed byte array | |
var point = ecParams.Curve.DecodePoint(bytes.ToArray()); | |
ECPublicKeyParameters decompressedKey = new ECPublicKeyParameters("EC", point, SecObjectIdentifiers.SecP192r1); | |
Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned())); | |
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded()) + " (uncompressed)"); | |
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded(true)) + "(compressed)"); | |
Console.WriteLine(); | |
var pkBytes = string.Concat("04", "7CF014EDB89D61DA08B75EDD939BB3F6F5C9462F1F29A698BF5E047B20F5C8751E59382D1075B6FC138FA5339CD3E360").FromHexStr(); | |
var pkPoint = ecParams.Curve.DecodePoint(pkBytes.ToArray()); | |
Console.WriteLine("Loaded public key: " + ToHex(pkPoint.GetEncoded()) + " (uncompressed)"); | |
// shared secret test | |
var keyPairA = Crypto.CreateEccKeyPair(); | |
var keyPairB = Crypto.CreateEccKeyPair(); | |
var secretA = Crypto.CreateSharedSecret(keyPairB.publicKey.Q.GetEncoded(), keyPairA.privateKey.D.ToByteArray()); | |
var secretB = Crypto.CreateSharedSecret(keyPairA.publicKey.Q.GetEncoded(), keyPairB.privateKey.D.ToByteArray()); | |
var existingString = publicKey.Q.GetEncoded().ToList().Skip(1).ToList().ToHexStr().ToUpperInvariant().Slice(4); | |
existingString.Dump(); | |
Console.WriteLine($"Base 16 string length: {existingString.Length}\n"); | |
var hexString = bytes.ToHexStr().ToUpperInvariant().Slice(5); | |
hexString.Dump(); | |
Console.WriteLine($"Base 16 string length: {hexString.Length}\n"); | |
var base32 = Base32.Encode(bytes.ToArray()).Slice(4); | |
base32.Dump(); | |
Console.WriteLine($"Base 32 string length: {base32.Length}\n"); | |
var base58 = Base58.Encode(bytes.ToArray()).Slice(8); | |
base58.Dump(); | |
Console.WriteLine($"Base 58 string length: {base58.Length}\n"); | |
var base64 = Convert.ToBase64String(bytes.ToArray()).Slice(0); | |
base64.Dump(); | |
Console.WriteLine($"Base 64 string length: {base64.Length}\n"); | |
var base85=Ascii85.Encode(bytes.ToArray()).Slice(0); | |
base85.Dump(); | |
Console.WriteLine($"Base 85 string length: {base85.Length}\n"); | |
} | |
public static void CreateAsn1Key(AsymmetricCipherKeyPair keyPair) | |
{ | |
//ECPrivateKeyParameters privKey, ECPublicKeyParameters pubKey | |
var stefan = @"-----BEGIN EC PRIVATE KEY----- | |
MF8CAQEEGAS9qVyFO1coVxxzYWwBHZMfRUg31UJ5UqAKBggqhkjOPQMBAaE0AzIA | |
BNJ7ClSvMxCQm03TVWrZn/gVrvnbXU/+zEmK9kPpPpeUiZxnx/TK3btqFo9iI3D9 | |
sg== | |
-----END EC PRIVATE KEY-----"; | |
var pemReader = new PemReader(new StringReader(stefan)); | |
var pemObj = pemReader.ReadPemObject(); | |
var seq = (Asn1Sequence)Asn1Sequence.FromByteArray(pemObj.Content); | |
var kpriv = keyPair.Private as ECPrivateKeyParameters; | |
var kpub = keyPair.Public as ECPublicKeyParameters; | |
var vec = new Asn1EncodableVector(); | |
vec.Add(seq[0]); | |
var derOs = new DerOctetString(kpriv.D.ToByteArray()); | |
vec.Add(derOs); | |
vec.Add(seq[2]); | |
var dto = new DerTaggedObject(1, new DerBitString(kpub.Q.GetEncoded())); | |
vec.Add(dto); | |
var derSeq = new DerSequence(vec); | |
var sw = new StringWriter(); | |
var pemWriter = new PemWriter(sw); | |
pemWriter.WriteObject( | |
new Org.BouncyCastle.Utilities.IO.Pem.PemObject("EC PRIVATE KEY", | |
derSeq.GetDerEncoded()) | |
); | |
var result = sw.ToString(); | |
} | |
// Write custom extension methods here. They will be available to all queries. | |
public static class MyExtensions | |
{ | |
// Write custom extension methods here. They will be available to all queries. | |
public static string AsHexStr(this byte[] dataBytes, string separator = "") | |
{ | |
if (dataBytes == null) | |
{ | |
return string.Empty; | |
} | |
return string.Join(separator, dataBytes.Select(s => $"{s:X2}")); | |
} | |
public static byte[] FromHexStr(this string hexString) | |
{ | |
hexString = hexString.Replace(" ", string.Empty); | |
hexString = hexString.Replace("-", string.Empty); | |
hexString = hexString.Replace(":", string.Empty); | |
hexString = hexString.Replace(",", string.Empty); | |
hexString = hexString.Replace("0x", string.Empty); | |
hexString = hexString.Replace("\n", string.Empty); | |
hexString = hexString.Replace("\r", string.Empty); | |
hexString = hexString.Replace("\t", string.Empty); | |
return Enumerable.Range(0, hexString.Length) | |
.Where(x => x % 2 == 0) | |
.Select(x => Convert.ToByte(hexString.Substring(x, 2), 16)) | |
.ToArray(); | |
} | |
} | |
// Define other methods and classes here | |
public static string ToHex(byte[] bytes) | |
{ | |
return BitConverter.ToString(bytes).Replace("-", ""); | |
} | |
public class Crypto | |
{ | |
public static (ECPrivateKeyParameters privateKey, ECPublicKeyParameters publicKey) CreateEccKeyPair(string curve = "secp256r1") | |
{ | |
var rnd = new Random(); | |
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName(curve); | |
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, | |
ecParams.G, ecParams.N, ecParams.H, | |
ecParams.GetSeed()); | |
ECKeyGenerationParameters keyGenParams = | |
new ECKeyGenerationParameters(domainParameters, new SecureRandom()); | |
AsymmetricCipherKeyPair keyPair; | |
ECKeyPairGenerator generator = new ECKeyPairGenerator(); | |
generator.Init(keyGenParams); | |
keyPair = generator.GenerateKeyPair(); | |
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private; | |
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public; | |
return (privateKey, publicKey); | |
} | |
public static BigInteger CreateSharedSecret(byte[] publicKeyBytes, byte[] privateKeyBytes, string curveName="secp256r1") | |
{ | |
X9ECParameters ecP = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1"); | |
ECDomainParameters eCDomainParameters = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N); | |
ECCurve curve = eCDomainParameters.Curve; | |
ECPoint q = curve.DecodePoint(publicKeyBytes); | |
ECPublicKeyParameters oEcPublicKeyParameters = new ECPublicKeyParameters(q, eCDomainParameters); | |
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(privateKeyBytes), eCDomainParameters); | |
IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH"); | |
aKeyAgree.Init(privateKey); | |
BigInteger sharedSecret = aKeyAgree.CalculateAgreement(oEcPublicKeyParameters); | |
Console.WriteLine($"Shared secred: {sharedSecret} (Length: {sharedSecret.ToByteArrayUnsigned().Length})\n"); | |
return sharedSecret; | |
} | |
public static ECPoint Recover(byte[] hash, byte[] sigBytes, int rec) | |
{ | |
BigInteger r = new BigInteger(1, sigBytes, 0, 32); | |
BigInteger s = new BigInteger(1, sigBytes, 32, 32); | |
BigInteger[] sig = new BigInteger[] { r, s }; | |
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true); | |
return Q; | |
} | |
public static ECPoint ECDSA_SIG_recover_key_GFp(BigInteger[] sig, byte[] hash, int recid, bool check) | |
{ | |
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); | |
int i = recid / 2; | |
Console.WriteLine("r: " + ToHex(sig[0].ToByteArrayUnsigned())); | |
Console.WriteLine("s: " + ToHex(sig[1].ToByteArrayUnsigned())); | |
BigInteger order = ecParams.N; | |
BigInteger field = (ecParams.Curve as FpCurve).Q; | |
BigInteger x = order.Multiply(new BigInteger(i.ToString())).Add(sig[0]); | |
if (x.CompareTo(field) >= 0) throw new Exception("X too large"); | |
Console.WriteLine("Order: " + ToHex(order.ToByteArrayUnsigned())); | |
Console.WriteLine("Field: " + ToHex(field.ToByteArrayUnsigned())); | |
byte[] compressedPoint = new Byte[x.ToByteArrayUnsigned().Length + 1]; | |
compressedPoint[0] = (byte)(0x02 + (recid % 2)); | |
Buffer.BlockCopy(x.ToByteArrayUnsigned(), 0, compressedPoint, 1, compressedPoint.Length - 1); | |
ECPoint R = ecParams.Curve.DecodePoint(compressedPoint); | |
Console.WriteLine("R: " + ToHex(R.GetEncoded())); | |
if (check) | |
{ | |
ECPoint O = R.Multiply(order); | |
if (!O.IsInfinity) throw new Exception("Check failed"); | |
} | |
int n = (ecParams.Curve as FpCurve).Q.ToByteArrayUnsigned().Length * 8; | |
BigInteger e = new BigInteger(1, hash); | |
if (8 * hash.Length > n) | |
{ | |
e = e.ShiftRight(8 - (n & 7)); | |
} | |
e = BigInteger.Zero.Subtract(e).Mod(order); | |
BigInteger rr = sig[0].ModInverse(order); | |
BigInteger sor = sig[1].Multiply(rr).Mod(order); | |
BigInteger eor = e.Multiply(rr).Mod(order); | |
ECPoint Q = ecParams.G.Multiply(eor).Add(R.Multiply(sor)); | |
Console.WriteLine("n: " + n); | |
Console.WriteLine("e: " + ToHex(e.ToByteArrayUnsigned())); | |
Console.WriteLine("rr: " + ToHex(rr.ToByteArrayUnsigned())); | |
Console.WriteLine("sor: " + ToHex(sor.ToByteArrayUnsigned())); | |
Console.WriteLine("eor: " + ToHex(eor.ToByteArrayUnsigned())); | |
Console.WriteLine("Q: " + ToHex(Q.GetEncoded())); | |
return Q; | |
} | |
public static byte[] CreateSignature(byte[] message, ECPrivateKeyParameters privateKey) | |
{ | |
ECDsaSigner signer = new ECDsaSigner(); | |
signer.Init(true, privateKey); | |
var sha = System.Security.Cryptography.SHA256Managed.Create(); | |
var hash = sha.ComputeHash(message); | |
BigInteger[] sig = signer.GenerateSignature(hash); | |
byte[] sigBytes = new byte[64]; | |
var r = sig[0].ToByteArrayUnsigned(); | |
var s = sig[1].ToByteArrayUnsigned(); | |
Buffer.BlockCopy(r, 0, sigBytes, 32-r.Length, r.Length); | |
Buffer.BlockCopy(s, 0, sigBytes, 32+32-s.Length, s.Length); | |
return sigBytes; | |
} | |
public static bool VerifySignature(byte[] pubkey, byte[] message, byte[] sigBytes, bool parseDerSignature=true) | |
{ | |
var sha = System.Security.Cryptography.SHA256Managed.Create(); | |
var hash = sha.ComputeHash(message); | |
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1"); | |
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, | |
ecParams.G, ecParams.N, ecParams.H, | |
ecParams.GetSeed()); | |
BigInteger r,s; | |
if (parseDerSignature) | |
{ | |
Asn1InputStream bIn = new Asn1InputStream(new MemoryStream(sigBytes)); | |
DerSequence seq = bIn.ReadObject() as DerSequence; | |
var r11 = seq[0].GetEncoded().ToList().Skip(2).ToArray(); | |
var r21 = seq[1].GetEncoded().ToList().Skip(3).ToArray(); | |
r = new BigInteger(1, r11, 0, 32); | |
s = new BigInteger(1, r21, 0, 32); | |
} | |
else | |
{ | |
r = new BigInteger(1, sigBytes.Take(32).ToArray()); | |
s = new BigInteger(1, sigBytes.Skip(32).Take(32).ToArray()); | |
} | |
ECPublicKeyParameters publicKey = new ECPublicKeyParameters(ecParams.Curve.DecodePoint(pubkey), domainParameters); | |
ECDsaSigner signer = new ECDsaSigner(); | |
signer.Init(false, publicKey); | |
return signer.VerifySignature(hash, r, s); | |
} | |
public static void FullSignatureTest(byte[] hash) | |
{ | |
X9ECParameters ecParams = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); | |
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, | |
ecParams.G, ecParams.N, ecParams.H, | |
ecParams.GetSeed()); | |
ECKeyGenerationParameters keyGenParams = | |
new ECKeyGenerationParameters(domainParameters, new SecureRandom()); | |
AsymmetricCipherKeyPair keyPair; | |
ECKeyPairGenerator generator = new ECKeyPairGenerator(); | |
generator.Init(keyGenParams); | |
keyPair = generator.GenerateKeyPair(); | |
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.Private; | |
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.Public; | |
Console.WriteLine("Generated private key: " + ToHex(privateKey.D.ToByteArrayUnsigned())); | |
Console.WriteLine("Generated public key: " + ToHex(publicKey.Q.GetEncoded())); | |
ECDsaSigner signer = new ECDsaSigner(); | |
signer.Init(true, privateKey); | |
BigInteger[] sig = signer.GenerateSignature(hash); | |
int recid = -1; | |
for (int rec = 0; rec < 4; rec++) | |
{ | |
try | |
{ | |
ECPoint Q = ECDSA_SIG_recover_key_GFp(sig, hash, rec, true); | |
if (ToHex(publicKey.Q.GetEncoded()).Equals(ToHex(Q.GetEncoded()))) | |
{ | |
recid = rec; | |
break; | |
} | |
} | |
catch (Exception) | |
{ | |
continue; | |
} | |
} | |
if (recid < 0) throw new Exception("Did not find proper recid"); | |
byte[] fullSigBytes = new byte[65]; | |
fullSigBytes[0] = (byte)(27 + recid); | |
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, fullSigBytes, 1, 32); | |
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, fullSigBytes, 33, 32); | |
Console.WriteLine("Generated full signature: " + Convert.ToBase64String(fullSigBytes)); | |
byte[] sigBytes = new byte[64]; | |
Buffer.BlockCopy(sig[0].ToByteArrayUnsigned(), 0, sigBytes, 0, 32); | |
Buffer.BlockCopy(sig[1].ToByteArrayUnsigned(), 0, sigBytes, 32, 32); | |
ECPoint genQ = ECDSA_SIG_recover_key_GFp(sig, hash, recid, false); | |
Console.WriteLine("Generated signature verifies: " + VerifySignature(genQ.GetEncoded(), hash, sigBytes)); | |
} | |
} | |
public static class Ext | |
{ | |
public static string ToHexStr(this List<byte> bytes) | |
{ | |
StringBuilder sb = new StringBuilder(bytes.Count * 2); | |
for (int i = 0; i < bytes.Count; i++) | |
{ | |
sb.AppendFormat("{0:x2}", bytes[i]); | |
//sb.Dump(); | |
} | |
return sb.ToString(); | |
} | |
public static string Slice(this string s, int size) | |
{ | |
if (size==0 || size >= s.Length) | |
return s; | |
var sb = new StringBuilder(); | |
for (int i = 0; i < s.Length; i++) | |
{ | |
if (i > 0 && i % size == 0) | |
{ | |
sb.Append(" "); | |
} | |
// if (i > 0 && i % 24 == 0) | |
// { | |
// sb.Append("\n"); | |
// } | |
sb.Append(s[i]); | |
} | |
return sb.ToString(); | |
} | |
} | |
public static class Base58 | |
{ | |
private const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
public static string Encode(byte[] data) | |
{ | |
// Decode byte[] to BigInteger | |
System.Numerics.BigInteger intData = 0; | |
for (int i = 0; i < data.Length; i++) | |
{ | |
intData = intData * 256 + data[i]; | |
} | |
// Encode BigInteger to Base58 string | |
string result = ""; | |
while (intData > 0) | |
{ | |
int remainder = (int)(intData % 58); | |
intData /= 58; | |
result = Digits[remainder] + result; | |
} | |
// Append `1` for each leading 0 byte | |
for (int i = 0; i < data.Length && data[i] == 0; i++) | |
{ | |
result = '1' + result; | |
} | |
return result; | |
} | |
public static byte[] Decode(string s) | |
{ | |
// Decode Base58 string to BigInteger | |
System.Numerics.BigInteger intData = 0; | |
for (int i = 0; i < s.Length; i++) | |
{ | |
int digit = Digits.IndexOf(s[i]); //Slow | |
if (digit < 0) | |
throw new FormatException(string.Format("Invalid Base58 character `{0}` at position {1}", s[i], i)); | |
intData = intData * 58 + digit; | |
} | |
// Encode BigInteger to byte[] | |
// Leading zero bytes get encoded as leading `1` characters | |
int leadingZeroCount = s.TakeWhile(c => c == '1').Count(); | |
var leadingZeros = Enumerable.Repeat((byte)0, leadingZeroCount); | |
var bytesWithoutLeadingZeros = | |
intData.ToByteArray() | |
.Reverse()// to big endian | |
.SkipWhile(b => b == 0);//strip sign byte | |
var result = leadingZeros.Concat(bytesWithoutLeadingZeros).ToArray(); | |
return result; | |
} | |
} | |
public static class Base32 | |
{ | |
private static readonly char[] DIGITS; | |
private static readonly int MASK; | |
private static readonly int SHIFT; | |
private static Dictionary<char, int> CHAR_MAP = new Dictionary<char, int>(); | |
private const string SEPARATOR = "-"; | |
static Base32() | |
{ | |
DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray(); | |
MASK = DIGITS.Length - 1; | |
SHIFT = numberOfTrailingZeros(DIGITS.Length); | |
for (int i = 0; i < DIGITS.Length; i++) CHAR_MAP[DIGITS[i]] = i; | |
} | |
private static int numberOfTrailingZeros(int i) | |
{ | |
// HD, Figure 5-14 | |
int y; | |
if (i == 0) return 32; | |
int n = 31; | |
y = i << 16; if (y != 0) { n = n - 16; i = y; } | |
y = i << 8; if (y != 0) { n = n - 8; i = y; } | |
y = i << 4; if (y != 0) { n = n - 4; i = y; } | |
y = i << 2; if (y != 0) { n = n - 2; i = y; } | |
return n - (int)((uint)(i << 1) >> 31); | |
} | |
public static byte[] Decode(string encoded) | |
{ | |
// Remove whitespace and separators | |
encoded = encoded.Trim().Replace(SEPARATOR, ""); | |
// Remove padding. Note: the padding is used as hint to determine how many | |
// bits to decode from the last incomplete chunk (which is commented out | |
// below, so this may have been wrong to start with). | |
encoded = Regex.Replace(encoded, "[=]*$", ""); | |
// Canonicalize to all upper case | |
encoded = encoded.ToUpper(); | |
if (encoded.Length == 0) | |
{ | |
return new byte[0]; | |
} | |
int encodedLength = encoded.Length; | |
int outLength = encodedLength * SHIFT / 8; | |
byte[] result = new byte[outLength]; | |
int buffer = 0; | |
int next = 0; | |
int bitsLeft = 0; | |
foreach (char c in encoded.ToCharArray()) | |
{ | |
if (!CHAR_MAP.ContainsKey(c)) | |
{ | |
throw new DecodingException("Illegal character: " + c); | |
} | |
buffer <<= SHIFT; | |
buffer |= CHAR_MAP[c] & MASK; | |
bitsLeft += SHIFT; | |
if (bitsLeft >= 8) | |
{ | |
result[next++] = (byte)(buffer >> (bitsLeft - 8)); | |
bitsLeft -= 8; | |
} | |
} | |
// We'll ignore leftover bits for now. | |
// | |
// if (next != outLength || bitsLeft >= SHIFT) { | |
// throw new DecodingException("Bits left: " + bitsLeft); | |
// } | |
return result; | |
} | |
public static string Encode(byte[] data, bool padOutput = false) | |
{ | |
if (data.Length == 0) | |
{ | |
return ""; | |
} | |
// SHIFT is the number of bits per output character, so the length of the | |
// output is the length of the input multiplied by 8/SHIFT, rounded up. | |
if (data.Length >= (1 << 28)) | |
{ | |
// The computation below will fail, so don't do it. | |
throw new ArgumentOutOfRangeException("data"); | |
} | |
int outputLength = (data.Length * 8 + SHIFT - 1) / SHIFT; | |
StringBuilder result = new StringBuilder(outputLength); | |
int buffer = data[0]; | |
int next = 1; | |
int bitsLeft = 8; | |
while (bitsLeft > 0 || next < data.Length) | |
{ | |
if (bitsLeft < SHIFT) | |
{ | |
if (next < data.Length) | |
{ | |
buffer <<= 8; | |
buffer |= (data[next++] & 0xff); | |
bitsLeft += 8; | |
} | |
else | |
{ | |
int pad = SHIFT - bitsLeft; | |
buffer <<= pad; | |
bitsLeft += pad; | |
} | |
} | |
int index = MASK & (buffer >> (bitsLeft - SHIFT)); | |
bitsLeft -= SHIFT; | |
result.Append(DIGITS[index]); | |
} | |
if (padOutput) | |
{ | |
int padding = 8 - (result.Length % 8); | |
if (padding > 0) result.Append(new string('=', padding == 8 ? 0 : padding)); | |
} | |
return result.ToString(); | |
} | |
private class DecodingException : Exception | |
{ | |
public DecodingException(string message) : base(message) | |
{ | |
} | |
} | |
} | |
public static class Ascii85 | |
{ | |
/// <summary> | |
/// Encodes the specified byte array in Ascii85. | |
/// </summary> | |
/// <param name="bytes">The bytes to encode.</param> | |
/// <returns>An Ascii85-encoded string representing the input byte array.</returns> | |
public static string Encode(byte[] bytes) | |
{ | |
if (bytes == null) | |
throw new ArgumentNullException("bytes"); | |
// preallocate a StringBuilder with enough room to store the encoded bytes | |
StringBuilder sb = new StringBuilder(bytes.Length * 5 / 4); | |
// walk the bytes | |
int count = 0; | |
uint value = 0; | |
foreach (byte b in bytes) | |
{ | |
// build a 32-bit value from the bytes | |
value |= ((uint)b) << (24 - (count * 8)); | |
count++; | |
// every 32 bits, convert the previous 4 bytes into 5 Ascii85 characters | |
if (count == 4) | |
{ | |
if (value == 0) | |
sb.Append('z'); | |
else | |
EncodeValue(sb, value, 0); | |
count = 0; | |
value = 0; | |
} | |
} | |
// encode any remaining bytes (that weren't a multiple of 4) | |
if (count > 0) | |
EncodeValue(sb, value, 4 - count); | |
return sb.ToString(); | |
} | |
/// <summary> | |
/// Decodes the specified Ascii85 string into the corresponding byte array. | |
/// </summary> | |
/// <param name="encoded">The Ascii85 string.</param> | |
/// <returns>The decoded byte array.</returns> | |
public static byte[] Decode(string encoded) | |
{ | |
if (encoded == null) | |
throw new ArgumentNullException("encoded"); | |
// preallocate a memory stream with enough capacity to hold the decoded data | |
using (MemoryStream stream = new MemoryStream(encoded.Length * 4 / 5)) | |
{ | |
// walk the input string | |
int count = 0; | |
uint value = 0; | |
foreach (char ch in encoded) | |
{ | |
if (ch == 'z' && count == 0) | |
{ | |
// handle "z" block specially | |
DecodeValue(stream, value, 0); | |
} | |
else if (ch < c_firstCharacter || ch > c_lastCharacter) | |
{ | |
throw new FormatException("Invalid character '{0}' in Ascii85 block."); | |
} | |
else | |
{ | |
// build a 32-bit value from the input characters | |
try | |
{ | |
checked { value += (uint)(s_powersOf85[count] * (ch - c_firstCharacter)); } | |
} | |
catch (OverflowException ex) | |
{ | |
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex); | |
} | |
count++; | |
// every five characters, convert the characters into the equivalent byte array | |
if (count == 5) | |
{ | |
DecodeValue(stream, value, 0); | |
count = 0; | |
value = 0; | |
} | |
} | |
} | |
if (count == 1) | |
{ | |
throw new FormatException("The final Ascii85 block must contain more than one character."); | |
} | |
else if (count > 1) | |
{ | |
// decode any remaining characters | |
for (int padding = count; padding < 5; padding++) | |
{ | |
try | |
{ | |
checked { value += 84 * s_powersOf85[padding]; } | |
} | |
catch (OverflowException ex) | |
{ | |
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex); | |
} | |
} | |
DecodeValue(stream, value, 5 - count); | |
} | |
return stream.ToArray(); | |
} | |
} | |
// Writes the Ascii85 characters for a 32-bit value to a StringBuilder. | |
private static void EncodeValue(StringBuilder sb, uint value, int paddingBytes) | |
{ | |
char[] encoded = new char[5]; | |
for (int index = 4; index >= 0; index--) | |
{ | |
encoded[index] = (char)((value % 85) + c_firstCharacter); | |
value /= 85; | |
} | |
if (paddingBytes != 0) | |
Array.Resize(ref encoded, 5 - paddingBytes); | |
sb.Append(encoded); | |
} | |
// Writes the bytes of a 32-bit value to a stream. | |
private static void DecodeValue(Stream stream, uint value, int paddingChars) | |
{ | |
stream.WriteByte((byte)(value >> 24)); | |
if (paddingChars == 3) | |
return; | |
stream.WriteByte((byte)((value >> 16) & 0xFF)); | |
if (paddingChars == 2) | |
return; | |
stream.WriteByte(((byte)((value >> 8) & 0xFF))); | |
if (paddingChars == 1) | |
return; | |
stream.WriteByte((byte)(value & 0xFF)); | |
} | |
// the first and last characters used in the Ascii85 encoding character set | |
const char c_firstCharacter = '!'; | |
const char c_lastCharacter = 'u'; | |
static readonly uint[] s_powersOf85 = new uint[] { 85u * 85u * 85u * 85u, 85u * 85u * 85u, 85u * 85u, 85u, 1 }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment