Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Как современные вирусы шифровальщики используют ChaCha20. Криптография. ASM.

чвк белобрысые фембойчики
Разработчик
Разработчик
Статус
Оффлайн
Регистрация
21 Июн 2025
Сообщения
186
Реакции
74
Решил выпустить новую статью, делать нечего, заказов нет, команда чиллит, а делать что-то надо...
Статья сегодня на тему криптографии, а конкретно поговорим на тему алгоритма шифрования ChaCha20.
Сразу говорю, что должно быть понимание базовой школьной алгебры хотя бы на уровне базы, в статье много математический примером работы с алгоритмом.
Содержание статьи содержит в себе 3 главы введения, две из которых это теория, 3 практика с кодом.
4 глава же это полноценный разбор шифровальщики на примере LockBit 3.0, с объяснением того как рансом шифровал код.

Теория. Введение в криптографию
Криптография - это наука о математических методах обеспечения конфиденциальности, целостностиданных. Шифрование если простым языком.
Криптография изучает методы шифрования информации, обратимого преобразования открытого (исходного) текста на основе секретного
Пожалуйста, авторизуйтесь для просмотра ссылки.
или
Пожалуйста, авторизуйтесь для просмотра ссылки.
в
Пожалуйста, авторизуйтесь для просмотра ссылки.
(
Пожалуйста, авторизуйтесь для просмотра ссылки.
).

Шифрование это процесс обратимого преобразования открытого текста в шифротекст с использованием ключа.
Шифрование делится на симметричное и асимметричное, есть два вида шифрования:
1. Симметричное шифрование - это вид шифрования, когда для шифровки и расшифровки используется один и тот же ключ, этот ключ должен быть заранее известен обеим сторонам и передаваться по защищенному каналу так как его компрометация приводит к раскрытию всей информации
Само симметричное разделяется на виды: есть симметричное шифрование вида потокового, есть вида блока.
Потоковое шифрование - это когда данные обрабатывются последовательно, к примеру определенным количеством битов или байтов.
Ьлоковое шифрование - это когда данные сначала разбиваются на блоки фиксированного размера, после чего каждый блок шифруется отдельно с использованием одного и того же ключа, если последний блок оказывается меньше требуемого размера, применяется дополнение.
Пример криптографических алгоритмов с симметричным шифрованием: AES-256/ChaCha20.
1776266109788.png

Приведу пример, к примеру у нас есть функция:
c=(m+k)modn где m это исходное сообщение c шифротекст n размер алфавита
расшифрование выполняется обратной операцией:
m=(c−k)modn
например пусть m = 7 и ключ k = 3 при n = 10 тогда получаем (7 + 3) mod 10 = 0, в модульной арифметике отрицательные значения приводятся по модулю n, при расшифровании m = 0 - 3 mod 10 = 7, таким образом один и тот же ключ используется и для шифрования и для расшифрования.
В единой математической функции это можно записать так:
Ek(m)=(m+k)modn,Dk(c)=(c−k)modn=(c+(−k))modn
то есть шифрование это функция вида ek(m), расшифрование же Dk(C)
в кольце Z каждый элемент имеет аддитивный обратный −k, думаю хорошая демонстрация симметричного шифрования.
-
2. Асимметричное шифрование - это метод шифрованяи когда у вас используется пара ключей открытый и закрытый, открытый ключ может свободно передаваться и использоваться для шифрования данных тогда как закрытый ключ хранится в секрете и применяется для расшифрования.
То есть кто угодно может использовать открытый ключ для шифрования определенных данных, но для дешифровки нужно использовать закрытый ключ:
1776266054169.png

Пример такого алгоритма: RSA.
На примере возьмем RSA(пример учебный, но не безопасный!!!!)
1. Возьмем два простых числа p = 3 и q = 11, считаем n = p * q = 33
2. Считаем функцию эйлера ф(n) = (p - 1)(q - 1) = 2 * 10 = 20
3. Выбираем открытый показатель e = 3 такой что он взаимно прост с 20
4. Находим закрытый показатель d такой что e * d mod 20 = 1, получаем d = 7

к примеру:
открытый ключ это (e, n) = (3, 33)
закрытый ключ это (d, n) = (7, 33)

шифрование выполняется по формуле
c = m^e mod n
расшифрование:
m = c^d mod n
например пусть сообщение m = 4
шифруем c = 4^3 mod 33 = 64 mod 33 = 31
расшифруем m = 31^7 mod 33 = 4

2. Теория. Разбираем работу ChaCha20.
Термины и их значения:
plaintext - исходные данные шифрования
keystream - 64 байтовый блок псевдослучайных данных, полученный из состояния чачи и использумый для xor с plaintext;
nonce - уникальное значение, которое падается вместе с ключом и формирует начальное состояние алгоритма, не является секретным но обзяано быть уникальным при каждомс шифровании с одним и тем же ключом.
cчетчик counter - часть состояния, которая увеличивается на каждый 64-байтовый блок.
state - 16 * 32-bit, то есть 16 слов по 32 бита.
1. константы
2. ключ
3. счетчик
4. nonce
QuarterRound - ядро чачи, он перемешивает четыре 32-битных слова состояния, создавая диффузию. Диффузия - это свойство преобразования, при котором изменение одного бита входных данных приводит к распространению изменений на множество битов выходного результата.
После 20 раундов из этого состояния формируется блок keystream, который затем используется в операции XOR с данными
DoubleRound - пара проходов(CR/DR), обеспечивающая полное перемешивание всех 16 слов состояния(про нее тоже позже узнаете)

====================================================================================================================
Теперь переходим к сути данного треда, это разбор конкретноого алгоритма шифрования - чача20.
Chacha20 - это симметричный потоковый алгоритм шифрования который под капотом использует операцию xor но не с простым ключом а с псевдослучайным потоком байтов генерируемым на основе ключа nonce и счетчика.
Что значит потоковый? - это значит, что шифрование происходит не блоками фиксированного размера например как в AES, а последовательно по байтам, генерируется непрерывный поток псевдослучайных данных который затем поэлементно складывается с исходным сообщением через xor, благодаря этому можно шифровать данные произвольной длины без разбиения на блоки и обрабатывать поток данных в реальном времен.

Работает это так:
1. принимается 32 битное значение которое участвует в операции сложения по модулю 2^32, при этом результат всегда ограничен размером регистра и переполнение просто отбрасывается
2. далее результат проходит побитовое XOR преобразование с другим значением, что обеспечивает нелинейное смешивание битов и является обратимой операцией
3. затем применяется операция ROL, то есть циклический сдвиг битов влево внутри 32 битного слова, при котором вышедшие за старший бит значения не теряются а возвращаются в младшие разряды, это перераспределяет биты внутри слова и усиливает диффузию(диффузия это неравномерный процесс перешивания)
в результате этих трех операций формируется некое промежуточное состояние, которое все еще нельзя рассматривать как исходное значение, так как информация распределена по всему 32 битному пространству
4. далее это не шифрование само по себе, а часть процесса генерации ключевого потока, который в ChaCha20 формируется после многократного применения таких преобразований ко всей 16 словной матрице состояния
5. и уже только после получения ключевого потока выполняется финальный этап, где каждый байт данных поэлементно ксорится(xor) с байтом keystream, формируя шифротекст.
1776270876053.png

записываем алгоритм как функцию состояния:
state = (constants[4], key[8], counter[1], nonce[3])
далее применяем функцию раундов:
S20 = R"20"(S0)
R - один дабл раунд, который состоит из операций:
(x, y, z, w) / QR(x, y, z, w)
а QR:
QR(a, b, c, d)
a = a + b mod 2<32> - сложение по модулю 2<32>
d = ( d ⊕ a) < 16 - нелинейное смешивание XOR + циклический сдвиг
c = c + d mod 2<32> - усиление зависимости состояния через сложение
b = (b ⊕ c) < 12 - диффузия через XOR и ротацию битов
a = a + b mod 2<32> - повторное распространение влияния между словами
d = (d ⊕ a) < 8 - дополнительное нелинейное перемешивание состояния
c = c + d mod 2<32> - финальное связывание компонент состояния
b = (b ⊕ c) < 7 - завершающая стадия диффузии внутри четверки слов
после всех этих операций нужно сформировать keystream, это значит, простыми словами, что м получаем новый 64-байтовый блок псевдослучайных данных, который зависит от всего внутреннего состояния, а не от отдельных его частей, после чего этот блок и используется как поток ключа:
output_block = (state_after_20_rounds + original_state) mod 2^32
В итоге получается, что блок кейстрима = KS = T.
То есть как прошла последовательность:
1. мы взяли алгоритм исходного состояние S0
2. прогнали его через серию обратимых преобразования согласно спецификации чачи(сложение по модулю, xor, rol)
3. получили новое состояние T = F(S0, key, nonce, counter)
Далее из T формируется 64-байтовый keystream блок, который уже используется в операции шифрования:
C = P⊕ KS
где P - открытый текст, KS - псевдослучаный поток байтов, C - непосредственно шифротекст.
в симметричных алгоритмах дешифровка тоже происходит довольно просто, восстановление обратно тривиально:
P = C⊕ KS
Думаю ничего трудного здесь нет, обычный ксор с перешиванием данных..


2. Практика, реализация и разбор на ассемблере FASM.
На практике разберем небольшую библиотеку, чтобы все понять и применим ее для шифрования каких нибудь данных, например для шифрования определенного файла.
asm:
Expand Collapse Copy
    mov r8, state
    mov r9, key
    call chacha20_setupkey32
  
    mov r9, n
    call chacha20_setupiv
  
    mov r9, msg
    mov r10, cmsg
    movzx r11, byte [msize]
    call chacha20_encrypt
Первое обращаем внимание на то. что в r8 сохраняется значение из state это массив из 16 двойных слов(64 byte), который определен в секции .rdata:
asm:
Expand Collapse Copy
section '.rdata' data readable
state rd 16
в этот буфер процедура chacha20_setupkey32 поместит начальную матрицу 4x4, которая состоит из констант "expand 32-byte k", ключа, счетчика и нонса.
asm:
Expand Collapse Copy
chacha20_setupkey32:
    mov rax, r8
  
    mov rcx, chacha20_sigma
    mov edx, [rcx+4*0]
    mov [rax+4*0], edx
    mov edx, [rcx+4*1]
    mov [rax+4*1], edx
    mov edx, [rcx+4*2]
    mov [rax+4*2], edx
    mov edx, [rcx+4*3]
    mov [rax+4*3], edx
  
    mov rcx, r9
    mov edx, [rcx+4*0]
    mov [rax+4*4], edx
    mov edx, [rcx+4*1]
    mov [rax+4*5], edx
    mov edx, [rcx+4*2]
    mov [rax+4*6], edx
    mov edx, [rcx+4*3]
    mov [rax+4*7], edx
    mov edx, [rcx+4*4]
    mov [rax+4*8], edx
    mov edx, [rcx+4*5]
    mov [rax+4*9], edx
    mov edx, [rcx+4*6]
    mov [rax+4*10], edx
    mov edx, [rcx+4*7]
    mov [rax+4*11], edx
    ret
chacha20_setupkey32 логика такова, что он берёт указатель на state в r8 и указатель на ключ в r9. сначала она пишет в первые четыре позиции state константу "expand 32-byte k", которая лежит в chacha20_sigma:
asm:
Expand Collapse Copy
chacha20_sigma: db "expand 32-byte k"
применяется же это для спецификации чачи для версии с 256-битным ключом. потом копирует восемь слов ключа, начиная с индекса 4 и до 11 включительно. ключ должен лежать как массив из 8 двойных слов в little-endian. после этого
в state остаются незаполненными индексы 12,13,14,15. их потом заполнит chacha20_setupiv:
asm:
Expand Collapse Copy
chacha20_setupiv:
    mov rax, r8
    mov rdx, r9
    mov ecx, [rdx+4*0]
    mov [rax+4*12], ecx
    mov ecx, [rdx+4*1]
    mov [rax+4*13], ecx
    xor rcx,rcx
    mov [rax+4*14], ecx
    mov [rax+4*15], ecx
    ret
процедура сама по себе тупая, просто перекладывает байты из одного места в другое, без вычислений. возвращает управление через ret, ничего не меняя в регистрах кроме rax и rcx, которые использует как временные. на этом всё.
далее в коде вызывается метка chacha20_encrypt:
C:
Expand Collapse Copy
chacha20_encrypt:
    push rbp
    mov rbp, rsp
    sub rsp, 16*4 ; j
    sub rsp, 16*4 ; x
    sub rsp, 64 ; tmp
    mov rax, r11
    test rax, rax
    jz done
    push rsi
    push rdi
    push rbx
  
    mov rax, r8
    mov rbx, rbp
    sub rbx, 16*4
    mov rcx, 15
.jloop:
    mov edx, [rax+4*rcx]
    mov [rbx+4*rcx], edx
    dec rcx
    jge .jloop
ее задача зашифровать данные потоком чачи.
на вход получается что она получает регистры r8(state) и уже настроенным ключом и iv, r9 - это же указатель на открый текст, r10 же это указатель на буфер куда класть шифротекст, r11 размер данных в байтах.

дальше резервирует место на стеке под три локальных массива:
asm:
Expand Collapse Copy
    sub rsp, 16*4 ; j
    sub rsp, 16*4 ; x
    sub rsp, 64 ; tmp
первый это j, 16 слов по 4 байта, всего 64 байта, туда будет скопировано исходное состояние state перед началом обработки блока, чтобы потом после хеширования прибавить к нему результат и получить keystream. второй это x, ещё 64 байта под рабочую копию состояния для передачи в функцию хеша. третий это tmp, 64 байта под временный чбуфер на случай если последний блок данных меньше 64 байт.
То есть после тоо кеак исходное копируется в j, формируется рабочая копия x, которая передается в функцию хэширования:
asm:
Expand Collapse Copy
chacha20_hash:
    push rbp
    push rsi
    mov rbp, 10
.roundLoop:
    call chacha20_DoubleRound
    dec rbp
    jnz .roundLoop
    pop rsi
Она берет рабочее состояние x и последовательно применяет к нему 10 двойных раундов, это называется DoubleRound, в сумме 20 раундов которые полноситью соотвествуют спецификации чачи.
Каждый дабл раунд состиот из чередования CR/DR, в ходе которых все 16 слов состояния перемешиваются с помощью трех базовых операций которые я описывал выше(сложение по модулю, ксор и циклический сдвиг).
CR/DR это два способа примененияя одной и той же операции, но к разным наобором слов состояния
CR - обрабатывает состяоние по 4x4 матрицы:
(0, 4, 8, 12)
(1, 5, 9, 13)
(2, 6, 10, 14)
(3, 7, 11, 15)
То есть берутся вертикальные группы слов и к каждой применяется QuarterRound, это просто перешивает данные внутри колонок

DR же затем берет те же 16 слов, но уже в диагональных комбинациях:
(0, 5, 10, 15)
(1, 6, 11, 12)
(2, 7, 8, 13)
(3, 4, 9, 14)
И также перешивает уже между разными колонками.
После 20 раундов получается рабочее состояние, которое затем поэлементарно складывается с исхходным состоянием j по модулю 2<32>, что является финальным этапом формирования ключа.
-
После обработки каждого 64 байтного блока величвается счетчик в состоянии, это нужно для того, чтобыдля каждого следующего блока генерировался уникальный keystream.
Если же счетик не увеличить, то при одинаковых ключе и nonce будет получаться один и тот же поток, а значит одинаковый keystream будет накладываться на разные данные.
nonce - это уникальное значение, которое вместе с ключом используется для генерации ключа.
C:
Expand Collapse Copy
    sub rbp, 16*4
    inc qword [rbp+4*12] ;// инкремент/увелечение счетчика блока
    mov rax, [rbp+4*12
    test rax, rax
    jnz .noNoneOverflow
        inc qword [rbp+4*14]
    .noNoneOverflow:
    add rbp, 16*4
   
    mov rax, rbp
    sub rax, 16*4+16*4
    mov rbx, r10
    mov rcx, 15
Задача этой процедуры брать блок keystream-логики и обеспечивать корректную работу счетчика состояния для генерации уникального потока на каждый блок данных, после получения доступа к текущему состоянию выполняется обычный инкремент 32 битного счетчика, который йотвечает за номер блока в потоке шифрования.
Если происходит переполнение младшего слова счётчика, управление переносом переходит в старшее слово, что позволяет сохранить корректную непрерывность нумерации блоков в пределах 64-битного диапазона, после чего же вычисляется адрес текущего блока состояния, который используется для генерации keystream через функцию хеширования чачи. Полученный результат затем применяется к входным данным - каждый 32-битный элемент keystream поэлементно комбинируется с соответствующими байтами открытого текста через XOR, формируя шифротекст.

Кароче мне лень писать дальше, я надеюсь все понятно.
Работа этого алгоритма заключается в том, что просто берется определенное значение, ОНО складывается по модулю 2<32>, после чего это же значение XOR-ится и реализуется циклический сдвиг ROL, после всех этих манипуляций мы получаем новое значение, это значение просто XOR-ится и в итоге получаются расшифрованные данные.

Процесс шифрования:
333309

Где:
ASM:
Expand Collapse Copy
mov r8, state
mov r9, key
call chacha20_setupkey32
формируется базовое внутреннее состояние алгоритма(4x4 матрица), state - это буфер 16 * 32 слов = 512 бит, записываются консатнта вида:
c:
Expand Collapse Copy
chacha20_sigma: db "expand 32-byte k"
в итоге создается структура вида:
S = (C,K,?,?)

Далее процесс установки nonce и счетчика:
asm:
Expand Collapse Copy
mov r9, n
call chacha20_setupiv
заполняются оставшиеся поля состояния
nonce - уникальный идентификатор потока, а counter номер блока(0 при старте)
Итоговое состояние:
S0 = (C, K, counter, nonce

Далее уже идет процесс подготовки данных:
asm:
Expand Collapse Copy
mov r9, shellcode
mov r10, cmsg
movzx r11, byte [msize]
r9 - указывает на шеллкод
r10 - буфер для
r11 - длина данных
после чего вызывается функция шифрования call chacha20_encrypt, после которой происходит:
1. копирование состояние
2. выполнение 20 раундом чачи
3. генерация кейстрим блока из 64 байта
4. xor входными данными:
C = P ⊕ KS
asm:
Expand Collapse Copy
chacha20_encrypt:
    push rbp
    mov rbp, rsp
    sub rsp, 16*4 ; j
    sub rsp, 16*4 ; x
    sub rsp, 64 ; tmp
    mov rax, r11
    test rax, rax
    jz done
    push rsi
    push rdi
    push rbx
   
    mov rax, r8
    mov rbx, rbp
    sub rbx, 16*4
    mov rcx, 15
.jloop:
    mov edx, [rax+4*rcx]
    mov [rbx+4*rcx], edx
    dec rcx
    jge .jloop

.mainLoop:
    cmp r11, 64
    jge .dontUseTmp
        mov rbx, r9
        mov rcx, r11
        dec rcx
        mov rdx, rbp
        sub rdx, (16*4)+(16*4)+64
        .tmploop:
        mov al, byte [rbx+rcx]
        mov [rdx+rcx], al
        dec rcx
        jge .tmploop
        mov r9, rdx
        mov rax, r10
        mov r12, rax
        mov r10, rdx
    .dontUseTmp:
   
    mov rax, rbp
    sub rax, 16*4
    mov rbx, rbp
    sub rbx, (16*4)+(16*4)
    mov rcx, 15
.xloop:
    mov edx, [rax+4*rcx]
    mov [rbx+4*rcx], edx
    dec rcx
    jge .xloop
   
    mov rsi, rbp
    sub rsi, 16*4
    mov rdi, rbp
    sub rdi, (16*4)+(16*4)
    call chacha20_hash
    mov rax, r9
    mov rcx, 15
.hashloop:
    mov edx, [rax+4*rcx]
    xor [rdi+4*rcx], edx
    dec rcx
    jge .hashloop
   
    sub rbp, 16*4
    inc qword [rbp+4*12]
    mov rax, [rbp+4*12]
    test rax, rax
    jnz .noNoneOverflow
        inc qword [rbp+4*14]
    .noNoneOverflow:
    add rbp, 16*4
   
    mov rax, rbp
    sub rax, 16*4+16*4
    mov rbx, r10
    mov rcx, 15
.cipherloop:
    mov edx, [rax+4*rcx]
    mov [rbx+4*rcx], edx
    dec rcx
    jge .cipherloop
   
    mov rax, r11
    cmp rax, 64
    jg .noLastBE
        jge .noLastB
            mov rax, r10
            mov rbx, r12
            mov rcx, r11
            dec rcx
            .copyloop:
            mov dl, [rax+rcx]
            mov [rbx+rcx], dl
            dec rcx
            jge .copyloop
        .noLastB:
        mov rdx, r8
        sub rbp, 16*4
        mov rax, [rbp+4*12]
        mov [rdx+4*14], rax
        mov rax, [rbp+4*14]
        mov [rdx+4*15], rax
        add rbp, 16*4
        jmp cleanup
    .noLastBE:
   
    sub r11, 64
    add r10, 64
    add r9, 64
    jmp .mainLoop
   
cleanup:
    pop rbx
    pop rdi
    pop rsi
done:
    add rsp, 64
    add rsp, 16*4
    add rsp, 16*4
    pop rbp
    ret

в итоге вся выпочка детерминированное преобразование:
(C,K,nonce,counter) -> KS -> C
где безопасность определяется стойкостью генератора псевдослучайного потока, а XOR лишь применяется как финальная операция наложения

2. Технический анализ исходного кода алгоритма шифрования(LockBit3.0)
Эту часть статьи можно тоже отнести к практике, ведь мы будем разбирать реальный код, кароче интерпретируйте как хотите, не важно.
Локер разделяется на несколько заголовочных файлов и одним главным:
chacha.h - публичный криптографический интерфейс алгоритма.
внутри:
C:
Expand Collapse Copy
typedef struct {
    uint32_t input[16];
} chacha_ctx;
16 x 32 битных слова - 512 бит, этов нутреннее состояние чачи(4x4 матрицы), хранит:
1. константы
2. ключ
3. счетчик
4. nonce.
прям в точности как мы разбирали в статье:)
Также вызывается парочка функций:
1776271933752.png

Первая функция chacha_keysetup загружает ключ, строит базовое состояние через(константа + ключ).
Аналог инициализации: state[0..11]
Вторая функция chacha_ivsetup загружает IV / nonce, после чего задает уникальность потока.
Третья функция chacha_encrypt стоит в роле основного потокового xor-движка, его задача это сделать генерацию keystream, ксор с plaintexto-м, это одинаковая функция для шифрования/дешифрования.
encrypt-machine.h - это слой адптации под железо и компилятор, разбирать не буду.
encrypt-sync.h - выполняет задачу стандартного интерфейса потокового шифра.

chacha.c - main, начнем разбор главного .C-шного файла.
В макросах определяется алгебра состояния:
cc:
Expand Collapse Copy
#define ROTATE(v,c) (ROTL32(v,c))
#define XOR(v,w) ((v) ^ (w))
#define PLUS(v,w) (U32V((v) + (w)))
PLUS - сложение по модулю 2<32>
XOR - операция векторного пространства GF(2)
ROTATE - перестановка битов
PLUSONE - инкремент счетичка битов.

Теперь разберу только самые важные функции, которые поймет читатель который понял базовый принцип работы Чачи.
[CODE lang="c" title="C"]void ECRYPT_keysetup(ECRYPT_ctx* x, const u8* k, u32 kbits, u32 ivbits)
Эта функия строит 512 битное состояние генератора:
C:
Expand Collapse Copy
    const char* constants;

    x->input[4] = U8TO32_LITTLE(k + 0);
    x->input[5] = U8TO32_LITTLE(k + 4);
    x->input[6] = U8TO32_LITTLE(k + 8);
    x->input[7] = U8TO32_LITTLE(k + 12);
    if (kbits == 256) { /* recommended */
        k += 16;
        constants = sigma;
    }
    else { /* kbits == 128 */
        constants = tau;
    }
    x->input[8] = U8TO32_LITTLE(k + 0);
    x->input[9] = U8TO32_LITTLE(k + 4);
    x->input[10] = U8TO32_LITTLE(k + 8);
    x->input[11] = U8TO32_LITTLE(k + 12);
    x->input[0] = U8TO32_LITTLE(constants + 0);
    x->input[1] = U8TO32_LITTLE(constants + 4);
    x->input[2] = U8TO32_LITTLE(constants + 8);
    x->input[3] = U8TO32_LITTLE(constants + 12);
В итоге мы получаем структуру вида:
C:
Expand Collapse Copy
STATE = [
  CONSTANTS (128 bit),
  KEY       (256 or 128 bit),
  COUNTER   (64 bit),
  NONCE     (64 bit)
]
То есть эта функция является начальной точки дитнамической системы.

ENCRYPT_ivsetup:
C:
Expand Collapse Copy
void ECRYPT_ivsetup(ECRYPT_ctx* x, const u8* iv)
{
    x->input[12] = 0;
    x->input[13] = 0;
    x->input[14] = U8TO32_LITTLE(iv + 0);
    x->input[15] = U8TO32_LITTLE(iv + 4);
}
Суть этой функции в том, что онафиксирует параметр уникальности потока(nonce) и обнуляет счетчик блока внутри состояния.
Сброс счетчика генерации:
с:
Expand Collapse Copy
x->input[12] = 0;
x->input[13] = 0;
текущий номер блока = 0, 64 битный счетчик начинается заново, делается это для того, чтобы каждый новый поток шифрования стартовал с чистого состояния счетчика.
Далее задача nonce:
C:
Expand Collapse Copy
x->input[14] = U8TO32_LITTLE(iv + 0);
x->input[15] = U8TO32_LITTLE(iv + 4);
Это 64 битное значение nonce, которая интерпретируется как два 32 битных слова.

Далее ECRYPT_encrypt_bytes:
C:
Expand Collapse Copy
void ECRYPT_encrypt_bytes(ECRYPT_ctx* x, const u8* m, u8* c, u32 bytes)
{
    u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
    u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
    u8* ctarget = 0;
    u8 tmp[64];
    int i;

    if (!bytes) return;

    j0 = x->input[0];
    j1 = x->input[1];
    j2 = x->input[2];
    j3 = x->input[3];
    j4 = x->input[4];
    j5 = x->input[5];
    j6 = x->input[6];
    j7 = x->input[7];
    j8 = x->input[8];
    j9 = x->input[9];
    j10 = x->input[10];
    j11 = x->input[11];
    j12 = x->input[12];
    j13 = x->input[13];
    j14 = x->input[14];
    j15 = x->input[15];

    for (;;) {
        if (bytes < 64) {
            for (i = 0; i < bytes; ++i) tmp[i] = m[i];
            m = tmp;
            ctarget = c;
            c = tmp;
        }
        x0 = j0;
        x1 = j1;
        x2 = j2;
        x3 = j3;
        x4 = j4;
        x5 = j5;
        x6 = j6;
        x7 = j7;
        x8 = j8;
        x9 = j9;
        x10 = j10;
        x11 = j11;
        x12 = j12;
        x13 = j13;
        x14 = j14;
        x15 = j15;
        for (i = 8; i > 0; i -= 2) {
            QUARTERROUND(x0, x4, x8, x12)
                QUARTERROUND(x1, x5, x9, x13)
                QUARTERROUND(x2, x6, x10, x14)
                QUARTERROUND(x3, x7, x11, x15)
                QUARTERROUND(x0, x5, x10, x15)
                QUARTERROUND(x1, x6, x11, x12)
                QUARTERROUND(x2, x7, x8, x13)
                QUARTERROUND(x3, x4, x9, x14)
        }
        x0 = PLUS(x0, j0);
        x1 = PLUS(x1, j1);
        x2 = PLUS(x2, j2);
        x3 = PLUS(x3, j3);
        x4 = PLUS(x4, j4);
        x5 = PLUS(x5, j5);
        x6 = PLUS(x6, j6);
        x7 = PLUS(x7, j7);
        x8 = PLUS(x8, j8);
        x9 = PLUS(x9, j9);
        x10 = PLUS(x10, j10);
        x11 = PLUS(x11, j11);
        x12 = PLUS(x12, j12);
        x13 = PLUS(x13, j13);
        x14 = PLUS(x14, j14);
        x15 = PLUS(x15, j15);

        x0 = XOR(x0, U8TO32_LITTLE(m + 0));
        x1 = XOR(x1, U8TO32_LITTLE(m + 4));
        x2 = XOR(x2, U8TO32_LITTLE(m + 8));
        x3 = XOR(x3, U8TO32_LITTLE(m + 12));
        x4 = XOR(x4, U8TO32_LITTLE(m + 16));
        x5 = XOR(x5, U8TO32_LITTLE(m + 20));
        x6 = XOR(x6, U8TO32_LITTLE(m + 24));
        x7 = XOR(x7, U8TO32_LITTLE(m + 28));
        x8 = XOR(x8, U8TO32_LITTLE(m + 32));
        x9 = XOR(x9, U8TO32_LITTLE(m + 36));
        x10 = XOR(x10, U8TO32_LITTLE(m + 40));
        x11 = XOR(x11, U8TO32_LITTLE(m + 44));
        x12 = XOR(x12, U8TO32_LITTLE(m + 48));
        x13 = XOR(x13, U8TO32_LITTLE(m + 52));
        x14 = XOR(x14, U8TO32_LITTLE(m + 56));
        x15 = XOR(x15, U8TO32_LITTLE(m + 60));

        j12 = PLUSONE(j12);
        if (!j12) {
            j13 = PLUSONE(j13);
            /* stopping at 2^70 bytes per nonce is user's responsibility */
        }

        U32TO8_LITTLE(c + 0, x0);
        U32TO8_LITTLE(c + 4, x1);
        U32TO8_LITTLE(c + 8, x2);
        U32TO8_LITTLE(c + 12, x3);
        U32TO8_LITTLE(c + 16, x4);
        U32TO8_LITTLE(c + 20, x5);
        U32TO8_LITTLE(c + 24, x6);
        U32TO8_LITTLE(c + 28, x7);
        U32TO8_LITTLE(c + 32, x8);
        U32TO8_LITTLE(c + 36, x9);
        U32TO8_LITTLE(c + 40, x10);
        U32TO8_LITTLE(c + 44, x11);
        U32TO8_LITTLE(c + 48, x12);
        U32TO8_LITTLE(c + 52, x13);
        U32TO8_LITTLE(c + 56, x14);
        U32TO8_LITTLE(c + 60, x15);

        if (bytes <= 64) {
            if (bytes < 64) {
                for (i = 0; i < bytes; ++i) ctarget[i] = c[i];
            }
            x->input[12] = j12;
            x->input[13] = j13;
            return;
        }
        bytes -= 64;
        c += 64;
        m += 64;
    }
}
Тут буквально мозг алгоритма - QuarterRound, который мы разбирали чуть выше, разберем по частям кода:
Извлечение состояния:
C:
Expand Collapse Copy
j0..j15 = x->input[0..15];
перед началом обработки блока берется текущее внутреннее состояние шифра, это состояние включает в себя: ключ, nonce, счетчик блоков и фиксированные константы.

for (;;) { - это обработка потока данных по блокам 64 байта, т.к чача работает строго блоками по 64 байта чито равно 512 битам, каждая итерация цикла = один криптографический блок.

Далее идет обработка неполного блока:
C:
Expand Collapse Copy
if (bytes < 64) {
    for (i = 0; i < bytes; ++i) tmp[i] = m[i];
    m = tmp;
    ctarget = c;
    c = tmp;
}
если осталось меньше 64 байт данные копируются во временный буфер, обработка же идет как полного блока, результат соотвественно потом обрезается.

x0..x15 = j0..j15;
Тут создается локальная копия state, j - неизменяемое состояние, x - временная матрица для раундов

И 20 раундов перешивания:
C:
Expand Collapse Copy
for (i = 8; i > 0; i -= 2)
Обычный цикл раундов согласно спецификации алгоритма чачи, каждый проход выполняет CR/DR которые были описаны выше

CR(работа по столбцам)
C:
Expand Collapse Copy
QUARTERROUND(x0, x4, x8, x12)
QUARTERROUND(x1, x5, x9, x13)
QUARTERROUND(x2, x6, x10, x14)
QUARTERROUND(x3, x7, x11, x15)
Матрица 4x4 обрабатывается по вертикали, в виде эффекта получаем перешивание внутри столбцов и разрушение локальной структуры данных, обычное смешивание.

Далее идет DR(диагонали):
C:
Expand Collapse Copy
QUARTERROUND(x0, x5, x10, x15)
QUARTERROUND(x1, x6, x11, x12)
QUARTERROUND(x2, x7, x8, x13)
QUARTERROUND(x3, x4, x9, x14)
Перешивание идет по диагоналям матрицы, в виде эффекта получаем связь между всеми элементами state и полную диффузию по структуре

Далее финализация раунда:
c:
Expand Collapse Copy
        x0 = j0;
        x1 = j1;
        x2 = j2;
        x3 = j3;
        x4 = j4;
        x5 = j5;
        x6 = j6;
        x7 = j7;
        x8 = j8;
        x9 = j9;
        x10 = j10;
        x11 = j11;
        x12 = j12;
        x13 = j13;
        x14 = j14;
        x15 = j15;
результат раундов складывается с исходным состоянием, это нужно для того, что функция стала необратимой

Далее превращение state в keystream через КСОР:
с:
Expand Collapse Copy
        x0 = XOR(x0, U8TO32_LITTLE(m + 0));
        x1 = XOR(x1, U8TO32_LITTLE(m + 4));
        x2 = XOR(x2, U8TO32_LITTLE(m + 8));
        x3 = XOR(x3, U8TO32_LITTLE(m + 12));
        x4 = XOR(x4, U8TO32_LITTLE(m + 16));
        x5 = XOR(x5, U8TO32_LITTLE(m + 20));
        x6 = XOR(x6, U8TO32_LITTLE(m + 24));
        x7 = XOR(x7, U8TO32_LITTLE(m + 28));
        x8 = XOR(x8, U8TO32_LITTLE(m + 32));
        x9 = XOR(x9, U8TO32_LITTLE(m + 36));
        x10 = XOR(x10, U8TO32_LITTLE(m + 40));
        x11 = XOR(x11, U8TO32_LITTLE(m + 44));
        x12 = XOR(x12, U8TO32_LITTLE(m + 48));
        x13 = XOR(x13, U8TO32_LITTLE(m + 52));
        x14 = XOR(x14, U8TO32_LITTLE(m + 56));
        x15 = XOR(x15, U8TO32_LITTLE(m + 60));
Сгенерированный блок состояния ксорится с plaintext-ом
все по формуле: ciphertext = plaintext XOR keystream

И далее обновляется счетчик блока:
C:
Expand Collapse Copy
j12 = PLUSONE(j12);
if (!j12) j13 = PLUSONE(j13);

После чего результат записывается в выходной буфер:

c:
Expand Collapse Copy
U32TO8_LITTLE(c + 0, x0);
// и еще снизу есть, символы занимают не буду добавлять
U32TO8_LITTLE(c + 60, x15);
32 битные слова переводятся в байты

После чего блок завершается:

C:
Expand Collapse Copy
if (bytes <= 64) {
    // еще и снизу есть, символы занимают опять же
    x->input[12] = j12;
    x->input[13] = j13;
    return;
}

Думаю описал нормально, эта часть главы вышла для тех, кто не понял то, что было выше, или понял слабо.
Суть самого алгоритма шифрования я думаю понятно.

====================================================================================================================
Полезные материалы по криптографии:
1.
Пожалуйста, авторизуйтесь для просмотра ссылки.

Описание книги вкратце:
До появления настоящей монографии практикам приходилось тратить многие часы на поиск и изучение научной литературы, прежде чем они могли приступить к разработке криптографических приложений. Именно этот пробел восполняет книга "Прикладная криптография". Начав с целей засекречивания передачи данных и простейших примеров программ для достижения этих целей, Брюс Шнайер разворачивает перед читателем всю панораму практических результатов 20 лет исследований В книге Брюса Шнайера "Прикладная криптография. Протоколы, алгоритмы, исходные тексты на языке C(Си)" в деталях описаны принципы работы, реализации и примеры использования криптографических алгоритмов, является самой читаемой книгой по криптографии в мире!

Книга Брюса Шнайера Прикладная криптография: протоколы, алгоритмы, исходный код на языке C предназначена современным программистам и инженерам, которым необходимо использовать криптографию.
2.
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
3.
Пожалуйста, авторизуйтесь для просмотра ссылки.

Книга реальная криптография автор Дэвид Вонг это практическое введение в современные криптографические протоколы где основное внимание уделяется не математическим доказательствам а реальным механизмам работы шифрования аутентификации и обмена ключами в сетевых системах, в книге объясняется почему простые схемы вроде базового симметричного шифрования недостаточны без правильного управления ключами и как ошибки реализации приводят к критическим уязвимостям рассматриваются базовые примитивы такие как хеш функции поточные и блочные шифры генераторы псевдослучайных чисел и протоколы публичного ключа, отдельно разбираются практические схемы вроде TLS и обмена ключами Диффи Хеллмана с акцентом на то как они устроены на уровне протокола а не только формул, главная идея книги в том что криптография это не просто математика а система инженерных решений где важны контекст использования ошибки интеграции и модель угроз.

====================================================================================================================
На этом все, надеюсь статья была полезной, как по мне она немного поплыла, но вроде нормально, кто хоть как то разбирается поймет.
Писал под эпинефрином просто.
Пакааа
 

Вложения

  • 1776268536547.png
    1776268536547.png
    85.8 KB · Просмотры: 82
Назад
Сверху Снизу