Реализация MD5 в Swift

Я пишу свою собственную простую реализацию MD5 в Swift. Я использую его для генерации ключа WEP. (Чтобы сгенерировать 128-битный ключ WEP, вы можете использовать хеш-функцию MD5. Вы расширяете парольную фразу до длины 64 байта, объединяя ее с самой собой, а затем запуская ее через хеш-алгоритм MD5.) Я понимаю, что WEP небезопасен, но ограничение заключается в том, что я должен сгенерировать зашифрованный ключ WEP из фразы-пароля. К сожалению, я не могу использовать вместо этого WPA2.

Вот моя реализация ниже. Я думаю, что у меня где-то есть небольшая ошибка, которая сбрасывает все это. Если я попытаюсь сгенерировать хэш md5 из заданной строки, я сгенерирую неправильный хэш.

Кто-нибудь знает, где моя ошибка? Спасибо за ваше время.

Примечания к коду ниже:
— я создал оператор для выполнения циклических сдвигов во время основного цикла алгоритма md5.
— я использовал индексную переменную j для отслеживания переменных в {a,b,c,d}, которые вы передаете в функцию в каждом подраунде основного цикла. (Альтернативой может быть замена значения, хранящегося в каждой переменной, после каждой итерации.)

// MD5 Implementation
/** Circular Shift implementation for 32 bit unsigned integers **/
protocol CircularShiftable {
    func leftCircularShift(value: UInt32, amount: UInt32) -> UInt32
}
extension UInt32: CircularShiftable {
    func leftCircularShift(value: UInt32, amount: UInt32) -> UInt32 {
        print(String((value << UInt32(amount)), radix: 2))
        print(String((value >> (UInt32(32 - amount))), radix: 2))
        return (amount == 0) ? value : ((value << UInt32(amount)) | (value >> (UInt32(32 - amount))))
    }
}
infix operator <<< { associativity left precedence 160 }
func <<< <T: CircularShiftable>(lhs: T, rhs: UInt32) -> UInt32 {
    return lhs.leftCircularShift(lhs as! UInt32, amount: rhs)
}

    // t[i] indicates the integer part of 2^32*abs(sin(i)) where i is in radians
    private let t: [UInt32] =
        // Round 1
       [0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
    0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
    0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,
    0x6b901122,0xfd987193,0xa679438e,0x49b40821,
    // Round 2
    0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,
    0xd62f105d,0x2441453,0xd8a1e681,0xe7d3fbc8,
    0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,
    0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
    // Round 3
    0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,
    0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
    0x289b7ec6,0xeaa127fa,0xd4ef3085,0x4881d05,
    0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
    // Round 4
    0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,
    0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
    0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
    0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391]

// Each round uses a sequence of shift amounts in each operation
//       Operation number: 1  2   3   4   5  6   7   8   9  10  11  12  13 14  15  16
private let s: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
                           5, 9,  14, 20, 5, 9,  14, 20, 5, 9,  14, 20, 5, 9,  14, 20,
                           4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
                           6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]

// Each of the four rounds uses a specific nonlinear function in the set S = { F, G, H, I }
let F = { (X: UInt32, Y: UInt32, Z: UInt32) -> UInt32 in
    return (X & Y) | ((~X) & Z)
}
let G = { (X: UInt32, Y: UInt32, Z: UInt32) -> UInt32 in
    return (X & Y) | (Y & (~Z))
}
let H = { (X: UInt32, Y: UInt32, Z: UInt32) -> UInt32 in
    return X ^ Y ^ Z
}
let I = { (X: UInt32, Y: UInt32, Z: UInt32) -> UInt32 in
    return Y ^ (X | (~Z))
}

func wep_encrypt_128(passphrase: String) -> String {
    // Pad message length to 64 bytes in length & convert to 32 bit unsigned integer array
    let msg = pad_msg(passphrase)

    // For each chunk of 512 in resulting msg, process in the main loop of the algorithm
    let blocks = chunk_msg(msg)
    let md5_key = md5_main_loop(blocks)

    // Format the resulting md5 generated key
    // Testing key: FAE93BD77058E8EDFDE8BE5C39
    return (md5_key.reduce(String("")) { (key, piece) -> String in
        return key + String(format: "%2X", piece)
    })
}

func chunk_msg(msg: [UInt8]) -> [[UInt8]] {
    var blocks: [[UInt8]] = []
    for (var i = 0; i < msg.count/64; i++) {
       blocks.append([UInt8](msg[64*i..<(64*i+64)]))
    }
    return blocks
}

func pad_msg(passphrase: String) -> [UInt8] {
    // For WEP you take the initial passphrase and add it to itself until you end up at 64 bytes in length
    var msg = passphrase
    for (var i = 0; msg.characters.count < 64; i = (i+1) % passphrase.characters.count) {
        msg.append(passphrase[passphrase.startIndex.advancedBy(i)])
    }

    // For MD5 you want a msg padded such that the length is 64 bits shy of a multiple of 512 (1 bit, then as many 0s as needed)
    var unicode_byte_values: [UInt8] = msg.utf8.map({$0 as UInt8})
    unicode_byte_values.append(0x80)    // MD5 uses big endian for encoding of bits into bytes
    // Note: (x % 64) == 56 is true when x is 8 bytes less than a multiple of 512 bits
    while ((unicode_byte_values.count % 64) != 56) {
        unicode_byte_values.append(0x00)
    }

    // Append little endian representation of the pre-MD5 padded String length (should always be 64 bytes for WEP encryption)
    //64 * 8 = 512 bits = 00000000 00000000 00000000 00000000 00000000 00000000 00000010 00000000 in big endian...
    //64 * 8 = 512 bits = 00000000 10000000 00000000 00000000 00000000 00000000 00000000 00000000 in little endian...
    var length = [UInt8](count: 8, repeatedValue: 0)
    length[length.endIndex.advancedBy(-7)] = 0x80
    length = length.reverse()
    unicode_byte_values += length

    return unicode_byte_values
}

// Convert each block of 4 bytes to a 32 bit word (using little endian notation)
func convert_to_sub_blocks(b: [UInt8]) -> [UInt32] {
    var m_block: [UInt32] = []
    for (var i = 0; i < b.count; i+=4) {
        let BLK_A = UInt32(b[b.startIndex.advancedBy(i)])
        let BLK_B = UInt32(b[b.startIndex.advancedBy(i+1)]) << 8
        let BLK_C = UInt32(b[b.startIndex.advancedBy(i+2)]) << 16
        let BLK_D = UInt32(b[b.startIndex.advancedBy(i+3)]) << 24
        m_block.append(UInt32(BLK_A | BLK_B | BLK_C | BLK_D))
    }
    return m_block
}

func md5_main_loop(blocks: [[UInt8]]) -> [UInt8] {
    // Chaining variables for the MD5 algorithm
    var A: UInt32 = 0x67452301
    var B: UInt32 = 0xEFCDAB89
    var C: UInt32 = 0x98BADCFE
    var D: UInt32 = 0x10325476

    // FF(a,b,c,d,Mj,s,ti) => a = b + ((a + F(b,c,d) + Mj + ti) <<< s)
    var M = [UInt32]()
    for (var i = 0; i < blocks.count; i++) {

        // There are 16 32-bit message sub-blocks per each 512-bit block of input text
        M = convert_to_sub_blocks(blocks[i])

        // Initialize vars for round
        var round_vars: [UInt32] = [A, B, C, D]
        var j = 0

        // Each round has 16 operations, thus 4 * 16 = 64 operations
        for (var l = 0; l < 64; l++) {

            // Set indexing relative to j
            let a = j
            let b = (a == 3) ? 0 : j+1
            let c = (b == 3) ? 0 : b+1
            let d = (c == 3) ? 0 : c+1

            // Set operation to apply and index into message structure to access based off round position
            var op: (UInt32, UInt32, UInt32) -> UInt32
            var m_i: Int
            if (l < 16) {       // Round 1
                op = F
                m_i = l
            }
            else if (l < 32) {  // Round 2
                op = G
                m_i = (5 * l + 1) % 16
            }
            else if (l < 48) {  // Round 3
                op = H
                m_i = (3 * l + 5) % 16
            }
            else {              // Round 4
                op = I
                m_i = (7 * l) % 16
            }

            // Compute value for variable for sub-round
            round_vars[a] = round_vars[b] &+ (round_vars[a] &+ op(round_vars[b], round_vars[c], round_vars[d]) &+ M[m_i] &+ t[l] <<< s[l])

            // Shift index for cycling through round_vars
            if ((l % 16 == 0) && (l != 0)) { j = 0 }
            else { j = (j == 0) ? 3 : --j }
        }

        // Update chaining variables for next block of data
        A = A &+ round_vars[0]
        B = B &+ round_vars[1]
        C = C &+ round_vars[2]
        D = D &+ round_vars[3]
    }

    return form_hash([A,B,C,D])
}

// Forming the hash requires concatenating the final values stored in A,B,C,D
func form_hash(sub_blocks: [UInt32]) -> [UInt8] {
    var key_hash: [UInt8] = []
    for b in sub_blocks {
        key_hash += [UInt8((b      )  & 0xFF)]
        key_hash += [UInt8((b >>  8)  & 0xFF)]
        key_hash += [UInt8((b >> 16) & 0xFF)]
        key_hash += [UInt8((b >> 24) & 0xFF)]
    }
    return key_hash
}

// Testing key: FAE93BD77058E8EDFDE8BE5C39
wep_encrypt_128(psk)

person crispy_bacon    schedule 12.01.2016    source источник
comment
I think I have a minor bug somewhere Хорошо, но почему? Каким должен быть ожидаемый результат по сравнению с результатом, который вы получите? Спасибо.   -  person Eric Aya    schedule 12.01.2016
comment
@ЭрикД. Я использовал несколько различных онлайн-генераторов MD5 для вычисления хеша MD5 заданной строки, и все они согласуются друг с другом... но не согласуются с моим кодом. Например, если я хочу использовать строку «томатный сок», она будет расширена до 64 символов в длину как «томатный сок, томатный сок, томатный сок, томатный сок, томатный сок, помидоры». Если вы запустите это через функцию генерации хэша md5, вы получите «40c1a019290ac90ab56bb81f2c8c99ea», а моя реализация выше получит «7D8A33B1C783DD4B FB189AB6A906D3E». Я не генерирую правильный хэш md5.   -  person crispy_bacon    schedule 12.01.2016
comment
Хорошо, не был уверен в том, что вы имели в виду. Спасибо.   -  person Eric Aya    schedule 12.01.2016
comment
Без проблем. Я обновлю вопрос, чтобы прояснить это.   -  person crispy_bacon    schedule 12.01.2016
comment
Для реализации MD5 Swift см. MD5.swift от Марцин Кржижановский.   -  person zaph    schedule 12.01.2016
comment
Интересно, в чем-то он очень похож. Спасибо за ссылку! У кого-нибудь еще есть идеи?   -  person crispy_bacon    schedule 12.01.2016


Ответы (1)


Я заметил несколько опечаток здесь и там при сборке этой вещи:

  • Моя длина была рассчитана неправильно. Я должен был добавить два во 2-м байте с конца, а затем перевернуть его. (Я полностью запутался, когда задавал вопрос с порядком байтов и заменой байтов, поэтому в исходной реализации это было просто далеко. Я заметил это, когда выполнял некоторые шаги для алгоритма на бумаге.)
  • G был введен неправильно: он должен читаться как (X & Z) | (Y & (~Z)), где я случайно поставил Y вместо жирного шрифта Z

Спасибо за вашу помощь всем! Теперь это работает :)

person crispy_bacon    schedule 12.01.2016