// @file MySecret.cs
// @version 4.0.1a (2026-03-16T09:17Z)
// @author David Ireland <https://cryptosys.net/contact>
// @copyright 2007-26 DI Management Services Pty Ltd
// @license Apache-2.0

using System;
using System.Text;
using System.Diagnostics;
using CryptoSysAPI;
/*
Requires CryptoSys API to be installed on your system.
Available from <https://cryptosys.net/api.html>
 * EITHER include `CryptoSysAPI.cs` in this project
 * OR add a Reference to `diCrSysAPINet.dll` which should have been installed in
 * `C:\Program Files (x86)\CryptoSys\DotNet`.
*/

namespace MySecretForm
{
    /// <summary>
    /// MySecret crypto functions
    /// </summary>
    public class MySecret
    {
        // Internal constants
        private const int BLOCK_LEN = 8;
        private const int FRAME_LEN = 60;
        private const string FRAME_BEGIN = "-----BEGIN MYSECRET-----";
        private const string FRAME_END = "-----END MYSECRET-----";
        private const string FRAME_STUB = "-----BEGIN MYSECRET";

        public static int CrSysVersion()
        {
            return General.Version();
        }
        public static string MySecretEncryptV4(string strPlain, string strPassword)
        {
            // INPUT: plaintext string, password
            // OUTPUT: string of MySecret v4 base64-encoded ciphertext (with framing and CR-LFs)
            const int hdrlen = 4;
            const int crclen = 4;
            const int zhlen = 10;
            const int zval = 0x05;
            const int ivlen = 12;
            const int rblock_len = 8;

            if (string.IsNullOrEmpty(strPlain)) {
                Debug.WriteLine("ERROR: nothing to encrypt!");
                return string.Empty;
            }

            Debug.WriteLine("PLAINTEXT: [" + strPlain + "]");

            // CONVERT input string to byte array
            byte[] abPlain = Encoding.GetEncoding("UTF-8").GetBytes(strPlain);
            int nPlain = abPlain.Length;
            Debug.WriteLine("Plaintext length = " + nPlain + " bytes");

            // COMPUTE THE CRC-32 CHECKSUM of the original plaintext
            uint nCrc32 = (uint)Crc.Data(abPlain);
            Debug.WriteLine("CRC-32 = " + nCrc32.ToString("X"));

            // COMPRESS using ZLIB_Deflate function
            byte[] abCompr = Zlib.Deflate(abPlain);
            if (abCompr == null || abCompr.Length <= 0) {
                Debug.WriteLine("ERROR: compression failed.");
                return string.Empty;
            }
            int cmplen = abCompr.Length;
            Debug.WriteLine("Compressed length = " + cmplen + " bytes");

            // CREATE A PADDING STRING OF COMPLETELY RANDOM BYTES [8-71] bytes long
            int k = Rng.Number(0, 7);
            int r = Rng.Number(0, 7);
            int padlen = (k + 1) * rblock_len + r;
            Debug.WriteLine("Padding string = " + padlen + " bytes = 0x" + padlen.ToString("X"));

            byte[] abPad = Rng.NonceBytes(padlen);

            // COMPOSE the encryption block
            int blklen = zhlen + cmplen + crclen + padlen;

            byte[] abBlock = new byte[blklen];
            abBlock[0] = 0x5A;  // 'Z'
            abBlock[1] = zval;

            int iOffset = 2;
            // Add CLEN (4 bytes, big-endian)
            byte[] abNumber = EncodeInt4((uint)cmplen);
            Array.Copy(abNumber, 0, abBlock, iOffset, 4);
            iOffset += 4;

            // Add ULEN (4 bytes, big-endian)
            abNumber = EncodeInt4((uint)nPlain);
            Array.Copy(abNumber, 0, abBlock, iOffset, 4);
            iOffset += 4;

            // Add compressed data
            Array.Copy(abCompr, 0, abBlock, iOffset, cmplen);
            iOffset += cmplen;
            Wipe.Data(abCompr);

            // Add CRC-32 (4 bytes)
            abNumber = EncodeInt4(nCrc32);
            Array.Copy(abNumber, 0, abBlock, iOffset, crclen);
            iOffset += crclen;
            Wipe.Data(abNumber);

            // Add padding
            Array.Copy(abPad, 0, abBlock, iOffset, padlen);
            Debug.WriteLine("BLK=" + Cnv.ToHex(abBlock));
            Wipe.Data(abPad);

            // GENERATE a random initialization vector (12-byte nonce)
            byte[] abInitVec = Rng.NonceBytes(ivlen);
            Debug.WriteLine("IV=" + Cnv.ToHex(abInitVec));

            // CREATE the 256-bit key using stretching with the IV as salt
            byte[] abKey = MakeStretchedKeyV4(strPassword, abInitVec);
            Debug.WriteLine("KEY=" + Cnv.ToHex(abKey));

            // ENCRYPT THE BLOCK using CHACHA20-POLY1305
            // NB the output is 16 bytes longer than the input (blklen+taglen)
            abBlock = Aead.EncryptWithTag(abBlock, abKey, abInitVec, Aead.Algorithm.Chacha20_Poly1305);
            Wipe.Data(abKey);
            if (abBlock.Length == 0) {
                Debug.WriteLine("ERROR: encryption operation failed!");
                return string.Empty;
            }
            Debug.WriteLine("ENC=" + Cnv.ToHex(abBlock));

            // ADD THE MAIN HEADER to the ciphertext block
            byte[] abEncode = new byte[hdrlen + ivlen + abBlock.Length];
            abEncode[0] = 0x4D;  // "M"
            abEncode[1] = 0x59;  // "Y"
            abEncode[2] = 0xFB;  // v4
            abEncode[3] = 0x00;

            iOffset = hdrlen;
            Array.Copy(abInitVec, 0, abEncode, iOffset, ivlen);
            iOffset += ivlen;
            Array.Copy(abBlock, 0, abEncode, iOffset, abBlock.Length);
            Debug.WriteLine("TOB64=" + Cnv.ToHex(abEncode));


            // ENCODE TO BASE 64
            string strBase64 = Convert.ToBase64String(abEncode);

            // ADD FRAMING AND LINE-BREAKS every 60 chars
            string strOutput = FRAME_BEGIN + Environment.NewLine;
            int nChars = strBase64.Length;
            iOffset = 0;

            while (nChars > FRAME_LEN) {
                strOutput += strBase64.Substring(iOffset, FRAME_LEN) + Environment.NewLine;
                nChars -= FRAME_LEN;
                iOffset += FRAME_LEN;
            }

            // Append final line, if any
            if (nChars > 0) {
                strOutput += strBase64.Substring(iOffset, nChars) + Environment.NewLine;
            }

            strOutput += FRAME_END + Environment.NewLine;

            return strOutput;
        }

        public static string MySecretDecryptV4(byte[] abEncode, string strPassword)
        {
            // INPUT: password, Encoded byte array data already checked for v4 signature
            // OUTPUT: decrypted plaintext string or empty string on error
            const int hdrlen = 4;
            const int zhlen = 10;
            const int zval = 0x05;
            const int ivlen = 12;

            /*
                            |<-zhlen------------->|<--cmplen---------->|crclen|<-padlen>|
                            +-----+-------+-------+--------------------+------+---------+
                            |ZS(2)|CLEN(4)|ULEN(4)| Compressed data... |CRC(4)|PAD(8-71)|
                            +-----+-------+-------+--------------------+------+---------+
                            |<----------------------(encrypt this)--------------------->|<-taglen>|
            +------+--------+-----------------------------------------------------------+---------+
            |SIG(4)| IV(12) | Ciphertext...                                             | TAG(16) |
            +------+--------+-----------------------------------------------------------+---------+
            |hdrlen|<-ivlen>|<------------blklen------------------------------------------------->|
            |<--encodlen------------------------------------------------------------------------->|
            */

            int encodlen = abEncode.Length;
            Debug.WriteLine("Encoded data = " + encodlen + " bytes");
            int offset = hdrlen;

            // Copy the 12-byte nonce (IV)
            byte[] abInitVec = new byte[ivlen];
            Array.Copy(abEncode, offset, abInitVec, 0, ivlen);
            Debug.WriteLine("IV=" + Cnv.ToHex(abInitVec));

            // RE-CREATE THE KEY
            byte[] abKey = MakeStretchedKeyV4(strPassword, abInitVec);
            Debug.WriteLine("KEY=" + Cnv.ToHex(abKey));

            // Copy the decryption block *including* the tag
            offset += ivlen;
            int blklen = encodlen - offset;
            byte[] abBlock = new byte[blklen];
            Array.Copy(abEncode, offset, abBlock, 0, blklen);

            // DECRYPT the entire block including the TAG - this will fail if the TAG is invalid
            abBlock = Aead.DecryptWithTag(abBlock, abKey, abInitVec, Aead.Algorithm.Chacha20_Poly1305);
            Wipe.Data(abKey);
            if (abBlock.Length == 0) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // PARSE the v4 block: expecting 'Z' and 0x05
            if (abBlock[0] != 0x5A || abBlock[1] != zval) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // EXTRACT lengths from big-endian-encoded values
            offset = 2;
            uint cmplen = DecodeInt4(abBlock[offset], abBlock[offset + 1], abBlock[offset + 2], abBlock[offset + 3]);
            offset = 6;
            uint ptlen = DecodeInt4(abBlock[offset], abBlock[offset + 1], abBlock[offset + 2], abBlock[offset + 3]);
            Debug.WriteLine("Compressed length=" + cmplen + ", Uncompressed length=" + ptlen);

            // CHECK for reasonableness
            if (cmplen >= int.MaxValue || ptlen >= int.MaxValue || cmplen > blklen) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }
            
            // EXTRACT the 4-byte CRC-32 value
            offset = zhlen + (int)cmplen;
            uint crc_chk = DecodeInt4(abBlock[offset], abBlock[offset + 1], abBlock[offset + 2], abBlock[offset + 3]);
            Debug.WriteLine("CRC-32 value found = " + crc_chk.ToString("X"));

            // DECOMPRESS/INFLATE the plaintext
            byte[] abCompr = new byte[cmplen];
            Array.Copy(abBlock, zhlen, abCompr, 0, cmplen);

            byte[] abPlain = Zlib.Inflate(abCompr);
            if (abPlain == null || abPlain.Length < ptlen) {
                Debug.WriteLine("ERROR: Decryption error (inflate failed).");
                return string.Empty;
            }

            // COMPUTE the CRC-32 checksum for the recovered plaintext
            uint crc32 = (uint)Crc.Data(abPlain);
            Debug.WriteLine("CRC-32 = " + crc32.ToString("X"));

            // VERIFY that the checksums match
            if (crc32 != crc_chk) {
                Debug.WriteLine("ERROR: Decryption error (CRC checksum failed).");
                return string.Empty;
            }

            // DECODE the byte array to an output string
            return Encoding.GetEncoding("UTF-8").GetString(abPlain);
        }

        public static string MySecretDecrypt(string strInput, string strPassword)
        {
            // INPUT: password, string of MySecret v3 or v4 base64-encoded ciphertext
            // OUTPUT: decrypted plaintext string or empty string on error

            if (string.IsNullOrEmpty(strInput)) {
                Debug.WriteLine("ERROR: nothing to decrypt!");
                return string.Empty;
            }

            Debug.WriteLine("Input = " + strInput.Length + " bytes");

            // REMOVE FRAMING, get base64 data
            int nBeg = strInput.IndexOf(FRAME_STUB);
            if (nBeg < 0) {
                Debug.WriteLine("ERROR: Not valid MySecret data!");
                return string.Empty;
            }

            nBeg = nBeg + FRAME_STUB.Length;
            nBeg = strInput.IndexOf("-", nBeg);

            while (nBeg > 0 && nBeg < strInput.Length && strInput[nBeg] == '-') {
                nBeg++;
            }

            if (nBeg >= strInput.Length) {
                Debug.WriteLine("ERROR: Not valid MySecret data!");
                return string.Empty;
            }

            int nEnd = strInput.IndexOf(FRAME_END, nBeg);
            if (nEnd <= 0 || nEnd <= nBeg) {
                Debug.WriteLine("ERROR: Not valid MySecret data!");
                return string.Empty;
            }

            string strBase64 = strInput.Substring(nBeg, nEnd - nBeg);
            Debug.WriteLine("Base64=" + strBase64);

            // DECODE BASE64 to byte array
            byte[] abEncode = Cnv.FromBase64(strBase64);
            int nEncode = abEncode.Length;
            Debug.WriteLine("Encoded data = " + nEncode + " bytes");

            // Check signature
            if (nEncode < 15 || abEncode[0] != 0x4D || abEncode[1] != 0x59 || abEncode[3] != 0x00) {
                Debug.WriteLine("ERROR: Not MySecret data.");
                return string.Empty;
            }

            if (abEncode[2] == 0xFB) {
                Debug.WriteLine("Found MYSECRET signature for version 4");
                return MySecretDecryptV4(abEncode, strPassword);
            }

            else if (abEncode[2] != 0xFC) {
                Debug.WriteLine("ERROR: Sorry, we only decrypt versions 3 or 4.");
                return string.Empty;
            }
            // We have v3 data
            Debug.WriteLine("Found MYSECRET signature for version 3");

            // Copy the 8-byte IV
            byte[] abInitVec = new byte[BLOCK_LEN];
            Array.Copy(abEncode, 4, abInitVec, 0, BLOCK_LEN);
            Debug.WriteLine("IV=" + Cnv.ToHex(abInitVec));

            // RE-CREATE THE KEY
            byte[] abKey = MakeStretchedKeyV3(strPassword, abInitVec, BLOCK_LEN);
            Debug.WriteLine("KEY=" + Cnv.ToHex(abKey));

            // Copy the decryption block
            int iOffset = BLOCK_LEN + 4;
            int nBlock = nEncode - iOffset;
            byte[] abBlock = new byte[nBlock];
            Array.Copy(abEncode, iOffset, abBlock, 0, nBlock);

            // DECRYPT the entire block
            abBlock = Blowfish.Decrypt(abBlock, abKey, Mode.CBC, abInitVec);
            Wipe.Data(abKey);
            if (abBlock.Length == 0) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // PARSE the v3 block
            if (abBlock[0] != 0x5A || abBlock[1] != 0x04) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // EXTRACT lengths from big-endian-encoded values
            iOffset = 2;
            UInt32 nCompr = DecodeInt4(abBlock[iOffset], abBlock[iOffset + 1], abBlock[iOffset + 2], abBlock[iOffset + 3]);
            iOffset = 6;
            UInt32 nPlain = DecodeInt4(abBlock[iOffset], abBlock[iOffset + 1], abBlock[iOffset + 2], abBlock[iOffset + 3]);
            Debug.WriteLine("Compressed length=" + nCompr + ", Uncompressed length=" + nPlain);

            // CHECK for reasonableness
            if (nCompr < 0 || nPlain < 0 || nCompr > nBlock) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // EXTRACT length of padding string
            int nPad = abBlock[nBlock - 1];
            Debug.WriteLine("Padding string is " + nPad + " bytes");

            // CONFIRM all lengths now match
            if (nBlock != 10 + nCompr + 3 + nPad) {
                Debug.WriteLine("ERROR: Decryption error.");
                return string.Empty;
            }

            // CHECK GUARD BYTES
            iOffset = nBlock - nPad;
            for (int i = 0; i < 8; i++) {
                if (abBlock[iOffset + i] != (byte)nPad) {
                    Debug.WriteLine("ERROR: Decryption error.");
                    return string.Empty;
                }
            }

            // EXTRACT the 3-byte CRC-24 value
            iOffset = nBlock - nPad - 3;
            long nCrcChk = DecodeInt4(0x00, abBlock[iOffset], abBlock[iOffset + 1], abBlock[iOffset + 2]);
            Debug.WriteLine("CRC-24 value found = " + nCrcChk.ToString("X"));

            // DECOMPRESS the plaintext
            byte[] abCompr = new byte[nCompr];
            Array.Copy(abBlock, 10, abCompr, 0, nCompr);

            byte[] abPlain = Zlib.Inflate(abCompr);
            if (abPlain == null || abPlain.Length < nPlain) {
                Debug.WriteLine("ERROR: Decryption error (inflate failed).");
                return string.Empty;
            }

            // COMPUTE the CRC-24 checksum for the recovered plaintext
            long nCrc24 = CRC24_Bytes(abPlain);
            Debug.WriteLine("CRC-24 = " + nCrc24.ToString("X"));

            // VERIFY that the checksums match
            if (nCrc24 != nCrcChk) {
                Debug.WriteLine("ERROR: Decryption error (CRC checksum failed).");
                return string.Empty;
            }

            // DECODE the byte array to an output string
            return Encoding.GetEncoding("UTF-8").GetString(abPlain);
        }

        public static string MySecretEncryptV3(string strPlain, string strPassword)
        {
            // INPUT: plaintext string, password
            // OUTPUT: string of MySecret v3 base64-encoded ciphertext (with framing and CR-LFs)

            if (string.IsNullOrEmpty(strPlain)) {
                Debug.WriteLine("ERROR: nothing to encrypt!");
                return string.Empty;
            }

            Debug.WriteLine("PLAINTEXT: [" + strPlain + "]");

            // CONVERT input string to byte array
            byte[] abPlain = Encoding.GetEncoding("UTF-8").GetBytes(strPlain);
            int nPlain = abPlain.Length;
            Debug.WriteLine("Plaintext length = " + nPlain + " bytes");

            // COMPUTE THE CRC-24 CHECKSUM of the original plaintext
            uint nCrc24 = CRC24_Bytes(abPlain);
            Debug.WriteLine("CRC-24 = " + nCrc24.ToString("X"));

            // COMPRESS using ZLIB_Deflate function
            byte[] abCompr = Zlib.Deflate(abPlain);
            if (abCompr == null || abCompr.Length <= 0) {
                Debug.WriteLine("ERROR: compression failed.");
                return string.Empty;
            }
            int nCompr = abCompr.Length;
            Debug.WriteLine("Compressed length = " + nCompr + " bytes");

            // CREATE A PADDING STRING
            int nPad = BLOCK_LEN - (10 + nCompr + 3) % BLOCK_LEN;
            int k = Rng.Number(0, 7);
            Debug.WriteLine("Random blocks = " + k);
            nPad = nPad + (k + 1) * BLOCK_LEN;
            Debug.WriteLine("Padding string = " + nPad + " bytes = 0x" + nPad.ToString("X"));

            byte[] abPad = Rng.NonceBytes(nPad);

            // Set value of bytes in guard and final blocks equal to NPAD
            for (int i = 0; i < 8; i++) {
                abPad[i] = (byte)nPad;
            }
            for (int i = ((k + 1) * BLOCK_LEN); i < nPad; i++) {
                abPad[i] = (byte)nPad;
            }

            // COMPOSE the encryption block
            int nBlock = 10 + nCompr + 3 + nPad;
            Debug.WriteLine("Encryption block = " + nBlock + " bytes = " + (nBlock / BLOCK_LEN) + " blocks");

            byte[] abBlock = new byte[nBlock];
            abBlock[0] = 0x5A;  // 'Z'
            abBlock[1] = 0x04;

            int iOffset = 2;
            // Add CLEN (4 bytes, big-endian)
            byte[] abNumber = EncodeInt4((uint)nCompr);
            Array.Copy(abNumber, 0, abBlock, iOffset, 4);
            iOffset += 4;

            // Add ULEN (4 bytes, big-endian)
            abNumber = EncodeInt4((uint)nPlain);
            Array.Copy(abNumber, 0, abBlock, iOffset, 4);
            iOffset += 4;

            // Add compressed data
            Array.Copy(abCompr, 0, abBlock, iOffset, nCompr);
            iOffset += nCompr;
            Wipe.Data(abCompr);

            // Add CRC-24 (3 bytes)
            abNumber = EncodeInt4(nCrc24);
            Array.Copy(abNumber, 1, abBlock, iOffset, 3);
            iOffset += 3;
            Wipe.Data(abNumber);

            // Add padding
            Array.Copy(abPad, 0, abBlock, iOffset, nPad);
            Wipe.Data(abPad);

            // GENERATE a random 8-byte initialization vector
            byte[] abInitVec = Rng.NonceBytes(BLOCK_LEN);
            Debug.WriteLine("IV (hex)=" + Cnv.ToHex(abInitVec));

            // CREATE the 128-bit key using stretching with the IV as salt
            byte[] abKey = MakeStretchedKeyV3(strPassword, abInitVec, BLOCK_LEN);
            Debug.WriteLine("KEY (hex)=" + Cnv.ToHex(abKey));

            // ENCRYPT THE BLOCK using Blowfish in CBC mode
            abBlock = Blowfish.Encrypt(abBlock, abKey, Mode.CBC, abInitVec);
            Wipe.Data(abKey);
            if (abBlock.Length == 0) {
                Debug.WriteLine("ERROR: encryption operation failed!");
                return string.Empty;
            }

            // ADD THE MAIN HEADER to the ciphertext block
            byte[] abEncode = new byte[4 + BLOCK_LEN + nBlock];
            abEncode[0] = 0x4D;  // "M"
            abEncode[1] = 0x59;  // "Y"
            abEncode[2] = 0xFC;  // v3
            abEncode[3] = 0x00;

            iOffset = 4;
            Array.Copy(abInitVec, 0, abEncode, iOffset, BLOCK_LEN);
            iOffset += BLOCK_LEN;
            Array.Copy(abBlock, 0, abEncode, iOffset, nBlock);

            int nEncode = 4 + BLOCK_LEN + nBlock;

            // ENCODE TO BASE 64
            string strBase64 = Convert.ToBase64String(abEncode);

            // ADD FRAMING AND LINE-BREAKS every 60 chars
            string strOutput = FRAME_BEGIN + Environment.NewLine;
            int nChars = strBase64.Length;
            iOffset = 0;

            while (nChars > FRAME_LEN) {
                strOutput += strBase64.Substring(iOffset, FRAME_LEN) + Environment.NewLine;
                nChars -= FRAME_LEN;
                iOffset += FRAME_LEN;
            }

            // Append final line, if any
            if (nChars > 0) {
                strOutput += strBase64.Substring(iOffset, nChars) + Environment.NewLine;
            }

            strOutput += FRAME_END + Environment.NewLine;

            return strOutput;
        }



        // ******************
        // INTERNAL FUNCTIONS
        // ******************

        /// <summary>
        /// Make stretched key for v3 (using MD5)
        /// </summary>
        /// <param name="strPassword"></param>
        /// <param name="abSalt"></param>
        /// <returns>16-byte key</returns>
        private static byte[] MakeStretchedKeyV3(string strPassword, byte[] abSalt, int nSalt)
        {
            const int nKey = 16;  // 128-bit key
            const int stretch_count = 1024;
            byte[] abPassword = Encoding.GetEncoding("UTF-8").GetBytes(strPassword);
            int nPassword = abPassword.Length;

            // X(1) = MD5(p || s)
            byte[] abTemp = new byte[nPassword + nSalt];
            Array.Copy(abPassword, 0, abTemp, 0, nPassword);
            Array.Copy(abSalt, 0, abTemp, nPassword, nSalt);

            byte[] abDigest = Md5.BytesHash(abTemp);

            // For i = 2 to STRETCH_COUNT, set X(i) = MD5(X(i-1) || p || s)
            abTemp = new byte[abDigest.Length + nPassword + nSalt];
            for (int iCount = 2; iCount <= stretch_count; iCount++) {
                int iOffset = 0;
                Array.Copy(abDigest, 0, abTemp, iOffset, abDigest.Length);
                iOffset += abDigest.Length;
                Array.Copy(abPassword, 0, abTemp, iOffset, nPassword);
                iOffset += nPassword;
                Array.Copy(abSalt, 0, abTemp, iOffset, nSalt);

                abDigest = Md5.BytesHash(abTemp);
            }

            byte[] abKey = new byte[nKey];
            Array.Copy(abDigest, 0, abKey, 0, nKey);

            return abKey;
        }

        /// <summary>
        /// Compute a four-byte encoding of the integer i, most significant byte first
        /// </summary>
        /// <param name="iInput">Integer to be encoded</param>
        /// <returns>Byte array of 4 bytes</returns>
        private static byte[] EncodeInt4(uint iInput)
        {
            byte[] encoded = new byte[4];
            for (int i = 3; i >= 0; i--) {
                encoded[i] = (byte)(iInput & 0xFF);
                iInput >>= 8;
            }
            return encoded;
        }

        // Interpret a 4-byte input and decode to a big-endian 32-bit unsigned integer
        public static uint DecodeInt4(byte b0, byte b1, byte b2, byte b3)
        {
            return (uint)((b0 << 24) | (b1 << 16) | (b2 << 8) | b3);
        }
        
        /// <summary>
        /// Make stretched key for v4 (using SHA-256)
        /// </summary>
        /// <param name="strPassword"></param>
        /// <param name="abSalt"></param>
        /// <returns>32-byte k</returns>
        private static byte[] MakeStretchedKeyV4(string strPassword, byte[] abSalt)
        {
            const int keylen = 32;  // 256-bit key
            const int stretch_count = 2048;
            Hash oHash = Hash.Instance();
            uint counter;
            byte[] ctrbytes;
            byte[] digest;

            byte[] abPassword = Encoding.GetEncoding("UTF-8").GetBytes(strPassword);

            // X(1) = H(p || s || INT(1))
            counter = 1;
            ctrbytes = EncodeInt4(counter);
            oHash.Init(HashAlgorithm.Sha256);
            oHash.AddData(abPassword);
            oHash.AddData(abSalt);
            oHash.AddData(ctrbytes);
            digest = oHash.Final();

            // For i = 2 to STRETCH_COUNT_V4, set 
            // X(i) = H(X(i-1) || p || s || INT(i))
            for (counter = 2; counter <= stretch_count; counter++) {
                ctrbytes = EncodeInt4(counter);
                oHash.Init(HashAlgorithm.Sha256);
                oHash.AddData(digest);
                oHash.AddData(abPassword);
                oHash.AddData(abSalt);
                oHash.AddData(ctrbytes);
                digest = oHash.Final();
            }

            byte[] abKey = new byte[keylen];
            Array.Copy(digest, 0, abKey, 0, keylen);

            // Clean up
            Wipe.Data(digest);
            Wipe.Data(abPassword);

            return abKey;
        }

        // CRC-24 Implementation (for v3)
        public static uint CRC24_Bytes(byte[] abMessage)
        {
            const uint CRC24_INIT = 0xB704CE;
            const uint CRC24_POLY = 0x1864CFB;
            uint ulCRC = CRC24_INIT;
            foreach (byte b in abMessage) {
                ulCRC = ulCRC ^ ((uint)b << 16);
                for (int j = 0; j < 8; j++) {
                    ulCRC = ulCRC << 1;
                    if ((ulCRC & 0x1000000) != 0)
                        ulCRC = ulCRC ^ CRC24_POLY;
                }
            }
            return ulCRC & 0xFFFFFF;
        }
    }
}