using System; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Parameters; namespace Org.BouncyCastle.Crypto.Signers { /// ISO9796-2 - mechanism using a hash function with recovery (scheme 1) public class Iso9796d2Signer : ISignerWithRecovery { /// /// Return a reference to the recoveredMessage message. /// /// The full/partial recoveredMessage message. /// public byte[] GetRecoveredMessage() { return recoveredMessage; } public const int TrailerImplicit = 0xBC; public const int TrailerRipeMD160 = 0x31CC; public const int TrailerRipeMD128 = 0x32CC; public const int TrailerSha1 = 0x33CC; private IDigest digest; private IAsymmetricBlockCipher cipher; private int trailer; private int keyBits; private byte[] block; private byte[] mBuf; private int messageLength; private bool fullMessage; private byte[] recoveredMessage; /// /// Generate a signer for the with either implicit or explicit trailers /// for ISO9796-2. /// /// base cipher to use for signature creation/verification /// digest to use. /// whether or not the trailer is implicit or gives the hash. public Iso9796d2Signer( IAsymmetricBlockCipher cipher, IDigest digest, bool isImplicit) { this.cipher = cipher; this.digest = digest; if (isImplicit) { trailer = TrailerImplicit; } else { if (digest is Sha1Digest) { trailer = TrailerSha1; } else if (digest is RipeMD160Digest) { trailer = TrailerRipeMD160; } else if (digest is RipeMD128Digest) { trailer = TrailerRipeMD128; } else { throw new System.ArgumentException("no valid trailer for digest"); } } } /// Constructor for a signer with an explicit digest trailer. /// /// /// cipher to use. /// /// digest to sign with. /// public Iso9796d2Signer(IAsymmetricBlockCipher cipher, IDigest digest):this(cipher, digest, false) { } public string AlgorithmName { get { return digest.AlgorithmName + "with" + "ISO9796-2S1"; } } public virtual void Init(bool forSigning, ICipherParameters parameters) { RsaKeyParameters kParam = (RsaKeyParameters) parameters; cipher.Init(forSigning, kParam); keyBits = kParam.Modulus.BitLength; block = new byte[(keyBits + 7) / 8]; if (trailer == TrailerImplicit) { mBuf = new byte[block.Length - digest.GetDigestSize() - 2]; } else { mBuf = new byte[block.Length - digest.GetDigestSize() - 3]; } Reset(); } /// compare two byte arrays. private bool IsSameAs(byte[] a, byte[] b) { if (messageLength > mBuf.Length) { if (mBuf.Length > b.Length) { return false; } for (int i = 0; i != mBuf.Length; i++) { if (a[i] != b[i]) { return false; } } } else { if (messageLength != b.Length) { return false; } for (int i = 0; i != b.Length; i++) { if (a[i] != b[i]) { return false; } } } return true; } /// clear possible sensitive data private void ClearBlock( byte[] block) { Array.Clear(block, 0, block.Length); } /// update the internal digest with the byte b public void Update( byte input) { digest.Update(input); if (messageLength < mBuf.Length) { mBuf[messageLength] = input; } messageLength++; } /// update the internal digest with the byte array in public void BlockUpdate( byte[] input, int inOff, int length) { digest.BlockUpdate(input, inOff, length); if (messageLength < mBuf.Length) { for (int i = 0; i < length && (i + messageLength) < mBuf.Length; i++) { mBuf[messageLength + i] = input[inOff + i]; } } messageLength += length; } /// reset the internal state public virtual void Reset() { digest.Reset(); messageLength = 0; ClearBlock(mBuf); if (recoveredMessage != null) { ClearBlock(recoveredMessage); } recoveredMessage = null; fullMessage = false; } /// Generate a signature for the loaded message using the key we were /// initialised with. /// public virtual byte[] GenerateSignature() { int digSize = digest.GetDigestSize(); int t = 0; int delta = 0; if (trailer == TrailerImplicit) { t = 8; delta = block.Length - digSize - 1; digest.DoFinal(block, delta); block[block.Length - 1] = (byte) TrailerImplicit; } else { t = 16; delta = block.Length - digSize - 2; digest.DoFinal(block, delta); block[block.Length - 2] = (byte) ((uint)trailer >> 8); block[block.Length - 1] = (byte) trailer; } byte header = 0; int x = (digSize + messageLength) * 8 + t + 4 - keyBits; if (x > 0) { int mR = messageLength - ((x + 7) / 8); header = (byte) (0x60); delta -= mR; Array.Copy(mBuf, 0, block, delta, mR); } else { header = (byte) (0x40); delta -= messageLength; Array.Copy(mBuf, 0, block, delta, messageLength); } if ((delta - 1) > 0) { for (int i = delta - 1; i != 0; i--) { block[i] = (byte) 0xbb; } block[delta - 1] ^= (byte) 0x01; block[0] = (byte) 0x0b; block[0] |= header; } else { block[0] = (byte) 0x0a; block[0] |= header; } byte[] b = cipher.ProcessBlock(block, 0, block.Length); ClearBlock(mBuf); ClearBlock(block); return b; } /// return true if the signature represents a ISO9796-2 signature /// for the passed in message. /// public virtual bool VerifySignature(byte[] signature) { byte[] block = cipher.ProcessBlock(signature, 0, signature.Length); if (((block[0] & 0xC0) ^ 0x40) != 0) { ClearBlock(mBuf); ClearBlock(block); return false; } if (((block[block.Length - 1] & 0xF) ^ 0xC) != 0) { ClearBlock(mBuf); ClearBlock(block); return false; } int delta = 0; if (((block[block.Length - 1] & 0xFF) ^ 0xBC) == 0) { delta = 1; } else { int sigTrail = ((block[block.Length - 2] & 0xFF) << 8) | (block[block.Length - 1] & 0xFF); switch (sigTrail) { case TrailerRipeMD160: if (!(digest is RipeMD160Digest)) { throw new ArgumentException("signer should be initialised with RipeMD160"); } break; case TrailerSha1: if (!(digest is Sha1Digest)) { throw new ArgumentException("signer should be initialised with SHA1"); } break; case TrailerRipeMD128: if (!(digest is RipeMD128Digest)) { throw new ArgumentException("signer should be initialised with RipeMD128"); } break; default: throw new ArgumentException("unrecognised hash in signature"); } delta = 2; } // // find out how much padding we've got // int mStart = 0; for (; mStart != block.Length; mStart++) { if (((block[mStart] & 0x0f) ^ 0x0a) == 0) { break; } } mStart++; // // check the hashes // byte[] hash = new byte[digest.GetDigestSize()]; int off = block.Length - delta - hash.Length; // // there must be at least one byte of message string // if ((off - mStart) <= 0) { ClearBlock(mBuf); ClearBlock(block); return false; } // // if we contain the whole message as well, check the hash of that. // if ((block[0] & 0x20) == 0) { fullMessage = true; digest.Reset(); digest.BlockUpdate(block, mStart, off - mStart); digest.DoFinal(hash, 0); for (int i = 0; i != hash.Length; i++) { block[off + i] ^= hash[i]; if (block[off + i] != 0) { ClearBlock(mBuf); ClearBlock(block); return false; } } recoveredMessage = new byte[off - mStart]; Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); } else { fullMessage = false; digest.DoFinal(hash, 0); for (int i = 0; i != hash.Length; i++) { block[off + i] ^= hash[i]; if (block[off + i] != 0) { ClearBlock(mBuf); ClearBlock(block); return false; } } recoveredMessage = new byte[off - mStart]; Array.Copy(block, mStart, recoveredMessage, 0, recoveredMessage.Length); } // // if they've input a message check what we've recovered against // what was input. // if (messageLength != 0) { if (!IsSameAs(mBuf, recoveredMessage)) { ClearBlock(mBuf); ClearBlock(block); ClearBlock(recoveredMessage); return false; } } ClearBlock(mBuf); ClearBlock(block); return true; } /// /// Return true if the full message was recoveredMessage. /// /// true on full message recovery, false otherwise. /// public virtual bool HasFullMessage() { return fullMessage; } } }