Как преобразовать байтовый массив (хэш MD5) в строку (36 символов)? - PullRequest
4 голосов
/ 13 сентября 2011

У меня есть байтовый массив, который был создан с использованием хеш-функции.Я хотел бы преобразовать этот массив в строку.Пока все хорошо, это даст мне шестнадцатеричную строку.

Теперь я хотел бы использовать что-то отличное от шестнадцатеричных символов, я бы хотел закодировать байтовый массив с этими 36 символами: [az] [0-9] .

Как мне поступить?

Редактировать: причина, по которой я хотел бы это сделать, заключается в том, что я хотел бы иметь меньшую строку, чем шестнадцатеричная строка.

Ответы [ 7 ]

6 голосов
/ 13 сентября 2011

Я адаптировал свою базовую функцию преобразования произвольной длины из этого ответа на C #:

static string BaseConvert(string number, int fromBase, int toBase)
{
    var digits = "0123456789abcdefghijklmnopqrstuvwxyz";
    var length = number.Length;
    var result = string.Empty;

    var nibbles = number.Select(c => digits.IndexOf(c)).ToList();
    int newlen;
    do {
        var value = 0;
        newlen = 0;

        for (var i = 0; i < length; ++i) {
            value = value * fromBase + nibbles[i];
            if (value >= toBase) {
                if (newlen == nibbles.Count) {
                    nibbles.Add(0);
                }
                nibbles[newlen++] = value / toBase;
                value %= toBase;
            }
            else if (newlen > 0) {
                if (newlen == nibbles.Count) {
                    nibbles.Add(0);
                }
                nibbles[newlen++] = 0;
            }
        }
        length = newlen;
        result = digits[value] + result; //
    }
    while (newlen != 0);

    return result;
}

Поскольку он идет из PHP, он может быть не слишком идиоматическим C #, также нетпроверка достоверности параметров.Тем не менее, вы можете передать ей строку в шестнадцатеричном коде, и она будет отлично работать с

var result = BaseConvert(hexEncoded, 16, 36);

Это не точно , что вы просили, но кодирование byte[] в гекстривиальный.

увидеть его в действии .

3 голосов
/ 29 декабря 2012

Ранее сегодня вечером я натолкнулся на вопрос о пересмотре кода, вращающийся вокруг того же алгоритма, который обсуждается здесь.См .: https://codereview.stackexchange.com/questions/14084/base-36-encoding-of-a-byte-array/

Я представил улучшенную реализацию одного из своих предыдущих ответов (оба используют BigInteger).См .: https://codereview.stackexchange.com/a/20014/20654. Решение принимает байт [] и возвращает строку Base36.И оригинал, и мой содержат простую информацию о тестах.

Для полноты ниже приведен метод декодирования байта [] из строки.Я включу функцию кодирования по ссылке выше.См. Текст после этого блока кода для некоторой простой информации о тесте для декодирования.

const int kByteBitCount= 8; // number of bits in a byte
// constants that we use in FromBase36String and ToBase36String
const string kBase36Digits= "0123456789abcdefghijklmnopqrstuvwxyz";
static readonly double kBase36CharsLengthDivisor= Math.Log(kBase36Digits.Length, 2);
static readonly BigInteger kBigInt36= new BigInteger(36);

// assumes the input 'chars' is in big-endian ordering, MSB->LSB
static byte[] FromBase36String(string chars)
{
    var bi= new BigInteger();
    for (int x= 0; x < chars.Length; x++)
    {
        int i= kBase36Digits.IndexOf(chars[x]);
        if (i < 0) return null; // invalid character
        bi *= kBigInt36;
        bi += i;
    }

    return bi.ToByteArray();
}

// characters returned are in big-endian ordering, MSB->LSB
static string ToBase36String(byte[] bytes)
{
    // Estimate the result's length so we don't waste time realloc'ing
    int result_length= (int)
        Math.Ceiling(bytes.Length * kByteBitCount / kBase36CharsLengthDivisor);
    // We use a List so we don't have to CopyTo a StringBuilder's characters
    // to a char[], only to then Array.Reverse it later
    var result= new System.Collections.Generic.List<char>(result_length);

    var dividend= new BigInteger(bytes);
    // IsZero's computation is less complex than evaluating "dividend > 0"
    // which invokes BigInteger.CompareTo(BigInteger)
    while (!dividend.IsZero)
    {
        BigInteger remainder;
        dividend= BigInteger.DivRem(dividend, kBigInt36, out remainder);
        int digit_index= Math.Abs((int)remainder);
        result.Add(kBase36Digits[digit_index]);
    }

    // orientate the characters in big-endian ordering
    result.Reverse();
    // ToArray will also trim the excess chars used in length prediction
    return new string(result.ToArray());
}

"Тест 1234. Сделано немного больше!"кодирует в Base64 как «165kkoorqxin775ct82ist5ysteekll7kaqlcnnu6mfe7ag7e63b5»

Чтобы декодировать эту строку Base36 1 000 000 раз, на моей машине требуется 12,6558909 секунд (я использовал те же условия сборки и машины, которые были указаны в моем ответе на просмотр кода)

1014упомянул, что вы имели дело с байтом [] для хэша MD5, а не с его шестнадцатеричным строковым представлением, поэтому я думаю, что это решение обеспечит вам наименьшую нагрузку.
2 голосов
/ 13 сентября 2011

Использование BigInteger (требуется ссылка System.Numerics)

Использование BigInteger (требуется ссылка System.Numerics)

const string chars = "0123456789abcdefghijklmnopqrstuvwxyz";

// The result is padded with chars[0] to make the string length
// (int)Math.Ceiling(bytes.Length * 8 / Math.Log(chars.Length, 2))
// (so that for any value [0...0]-[255...255] of bytes the resulting
// string will have same length)
public static string ToBaseN(byte[] bytes, string chars, bool littleEndian = true, int len = -1)
{
    if (bytes.Length == 0 || len == 0)
    {
        return String.Empty;
    }

    // BigInteger saves in the last byte the sign. > 7F negative, 
    // <= 7F positive. 
    // If we have a "negative" number, we will prepend a 0 byte.
    byte[] bytes2;

    if (littleEndian)
    {
        if (bytes[bytes.Length - 1] <= 0x7F)
        {
            bytes2 = bytes;
        }
        else
        {
            // Note that Array.Resize doesn't modify the original array,
            // but creates a copy and sets the passed reference to the
            // new array
            bytes2 = bytes;
            Array.Resize(ref bytes2, bytes.Length + 1);
        }
    }
    else
    {
        bytes2 = new byte[bytes[0] > 0x7F ? bytes.Length + 1 : bytes.Length];

        // We copy and reverse the array
        for (int i = bytes.Length - 1, j = 0; i >= 0; i--, j++)
        {
            bytes2[j] = bytes[i];
        }
    }

    BigInteger bi = new BigInteger(bytes2);

    // A little optimization. We will do many divisions based on 
    // chars.Length .
    BigInteger length = chars.Length;

    // We pre-calc the length of the string. We know the bits of 
    // "information" of a byte are 8. Using Log2 we calc the bits of 
    // information of our new base. 
    if (len == -1)
    {
        len = (int)Math.Ceiling(bytes.Length * 8 / Math.Log(chars.Length, 2));
    }

    // We will build our string on a char[]
    var chs = new char[len];
    int chsIndex = 0;

    while (bi > 0)
    {
        BigInteger remainder;
        bi = BigInteger.DivRem(bi, length, out remainder);

        chs[littleEndian ? chsIndex : len - chsIndex - 1] = chars[(int)remainder];
        chsIndex++;

        if (chsIndex < 0)
        {
            if (bi > 0)
            {
                throw new OverflowException();
            }
        }
    }

    // We append the zeros that we skipped at the beginning
    if (littleEndian)
    {
        while (chsIndex < len)
        {
            chs[chsIndex] = chars[0];
            chsIndex++;
        }
    }
    else
    {
        while (chsIndex < len)
        {
            chs[len - chsIndex - 1] = chars[0];
            chsIndex++;
        }
    }

    return new string(chs);
}

public static byte[] FromBaseN(string str, string chars, bool littleEndian = true, int len = -1)
{
    if (str.Length == 0 || len == 0)
    {
        return new byte[0];
    }

    // This should be the maximum length of the byte[] array. It's 
    // the opposite of the one used in ToBaseN.
    // Note that it can be passed as a parameter
    if (len == -1)
    {
        len = (int)Math.Ceiling(str.Length * Math.Log(chars.Length, 2) / 8);
    }

    BigInteger bi = BigInteger.Zero;
    BigInteger length2 = chars.Length;
    BigInteger mult = BigInteger.One;

    for (int j = 0; j < str.Length; j++)
    {
        int ix = chars.IndexOf(littleEndian ? str[j] : str[str.Length - j - 1]);

        // We didn't find the character
        if (ix == -1)
        {
            throw new ArgumentOutOfRangeException();
        }

        bi += ix * mult;

        mult *= length2;
    }

    var bytes = bi.ToByteArray();

    int len2 = bytes.Length;

    // BigInteger adds a 0 byte for positive numbers that have the
    // last byte > 0x7F
    if (len2 >= 2 && bytes[len2 - 1] == 0)
    {
        len2--;
    }

    int len3 = Math.Min(len, len2);

    byte[] bytes2;

    if (littleEndian)
    {
        if (len == bytes.Length)
        {
            bytes2 = bytes;
        }
        else
        {
            bytes2 = new byte[len];
            Array.Copy(bytes, bytes2, len3);
        }
    }
    else
    {
        bytes2 = new byte[len];

        for (int i = 0; i < len3; i++)
        {
            bytes2[len - i - 1] = bytes[i];
        }
    }

    for (int i = len3; i < len2; i++)
    {
        if (bytes[i] != 0)
        {
            throw new OverflowException();
        }
    }

    return bytes2;
}

Имейте в виду, что они ДЕЙСТВИТЕЛЬНО медленные!ДЕЙСТВИТЕЛЬНО ДЕЙСТВИТЕЛЬНО медленно!(2 минуты за 100к).Чтобы ускорить их, вам, вероятно, потребуется переписать операцию деления / модификации, чтобы они работали непосредственно с буфером, вместо того, чтобы каждый раз воссоздавать блокноты, как это делает BigInteger.И все равно будет медленным.Проблема состоит в том, что время, необходимое для кодирования первого байта, составляет O (n), где n - длина байтового массива (это потому, что весь массив должен быть разделен на 36).Если только вы не хотите работать с блоками по 5 байт и терять несколько бит.Каждый символ Base36 несет около 5,169925001 бит.Таким образом, 8 из этих символов будут содержать 41,35940001 бит.Совсем рядом 40 байтов.

Обратите внимание, что эти методы могут работать как в режиме с прямым порядком байтов, так и в режиме с прямым порядком байтов.Порядковый порядок ввода и вывода одинаков.Оба метода принимают параметр len.Вы можете использовать его, чтобы обрезать лишние 0 (нули).Обратите внимание, что если вы попытаетесь сделать вывод слишком маленьким, чтобы вместить его, вы получите OverflowException.

2 голосов
/ 13 сентября 2011

Если вы хотите более короткую строку и можете принять [a-zA-Z0-9] и + и /, тогда посмотрите на Convert.ToBase64String

0 голосов
/ 13 сентября 2011

вы можете использовать модуль. В этом примере закодируйте ваш байтовый массив в строку [0-9] [a-z]. измените его, если хотите.

    public string byteToString(byte[] byteArr)
    {
        int i;
        char[] charArr = new char[byteArr.Length];
        for (i = 0; i < byteArr.Length; i++)
        {
            int byt = byteArr[i] % 36; // 36=num of availible charachters
            if (byt < 10)
            {
                charArr[i] = (char)(byt + 48); //if % result is a digit
            }
            else
            {
                charArr[i] = (char)(byt + 87); //if % result is a letter
            }
        }
        return new String(charArr);
    }

Если вы не хотите терять данные для декодирования, вы можете использовать этот пример:

    public string byteToString(byte[] byteArr)
    {
        int i;
        char[] charArr = new char[byteArr.Length*2];
        for (i = 0; i < byteArr.Length; i++)
        {
            charArr[2 * i] = (char)((int)byteArr[i] / 36+48);
            int byt = byteArr[i] % 36; // 36=num of availible charachters
            if (byt < 10)
            {
                charArr[2*i+1] = (char)(byt + 48); //if % result is a digit
            }
            else
            {
                charArr[2*i+1] = (char)(byt + 87); //if % result is a letter
            }
        }
        return new String(charArr);
    }

и теперь у вас есть строка двойной длины, когда нечетный символ умножается на 36, а четный символ - остаток. например: 200 = 36 * 5 + 20 => "5k".

0 голосов
/ 13 сентября 2011

Обычно используется степень 2 - таким образом, один символ отображается на фиксированное количество битов.Например, алфавит из 32 битов будет отображаться в 5 битах.Единственная проблема в этом случае состоит в том, как десериализовать строки переменной длины.

Для 36 битов вы можете обработать данные как большое число, а затем:

  • разделить на 36
  • добавить остаток как символ к вашему результату
  • повторять до тех пор, пока в результате деления не будет 0

Проще сказать, чем сделать.

0 голосов
/ 13 сентября 2011
System.Text.Encoding enc = System.Text.Encoding.ASCII;
string myString = enc.GetString(myByteArray);

Вы можете поиграть с нужной вам кодировкой:

System.Text.ASCIIEncoding,
System.Text.UnicodeEncoding,
System.Text.UTF7Encoding,
System.Text.UTF8Encoding

Для соответствия требованиям [a-z][0-9] вы можете использовать его:

Byte[] bytes = new Byte[] { 200, 180, 34 };
string result = String.Join("a", bytes.Select(x => x.ToString()).ToArray());

У вас будет строковое представлениебайтов с разделителем символов.Чтобы преобразовать обратно, вам нужно разделить и преобразовать string[] в byte[], используя тот же подход с .Select().

...