Pull to refresh

Генерация SHA-256 посредством SIMD (SSE-2) инструкций, в MMX и XMM регистрах, без использования памяти (почти)

Reading time5 min
Views3K

Сижу я значит спокойно, никого не трогаю, починяю примус, и вдруг как захотелось сгенерировать SHA-256 целиком внутри процессора на MASM64, без обращения к памяти, что прям места себе не нахожу.

По сути алгоритм SHA-256 состоит из двух частей, часто называемых Декомпрессией, когда из данных входящего сообщения генерируются дополнительные данные и Компрессии, когда эти данные сжимаются до сообщения фиксированной длины Hesh. Оба алгоритма, до определенной степени, могут работать параллельно, когда данные вычисленные на этапе Декомпрессии тут же подвергаются Компрессии, а результатам такой параллельной работы является поэтапное обновление Hesh.

Алгоритм Декомпрессии позволяет вычислять два новых значения за раз и требует для своей работы 16 dword, что автоматически приводит к тому что единственное подходящие для него место размещения SIMD регистры.

В регистрах XMM0-XMM3 размещаются непосредственно сами значения, а в регистрах XMM4-XMM5 производятся вычисления. По соглашению о вызовах эти регистры не сохраняют значения между вызовами функций и потому их значения не требуется сохранять, перед началом процедуры и восстанавливать по ее окончанию.

Скрытый текст
.code
align xmmword
Head:
; s0
	pshufd xmm4,xmm0,10100101b
	movdqa xmm5,xmm4
	psrld xmm5,3
	psrlq xmm4,7
	pxor xmm5,xmm4
	psrlq xmm4,11
	pxor xmm5,xmm4
; s0 + w[0]
	pshufd xmm5,xmm5,10001000b
	paddd xmm5,xmm0
; s0 + w[0] + w[9]
	pshufd xmm4,xmm2,10011001b
	paddd xmm5,xmm4
; w[i] + k[i] + h
	movdq2q mm7,xmm0
	paddd mm7,qword ptr[r9]
	paddd mm7,mm3
	shufps xmm0,xmm1,01001110b
	shufps xmm1,xmm2,01001110b
	shufps xmm2,xmm3,01001110b
	shufps xmm3,xmm5,01001110b
; s1
	pshufd xmm4,xmm3,01010000b
	movdqa xmm5,xmm4
	psrld xmm5,10
	psrlq xmm4,17
	pxor xmm5,xmm4
	psrlq xmm4,2
	pxor xmm5,xmm4
	pshufd xmm5,xmm5,10001000b
; s1 + s0 + w[0] + w[9]
	pslldq xmm5,8
	paddd xmm3,xmm5

К интересной особенности этого алгоритма можно отнести способ реализации вращения данных в SIMD регистрах. Стоит заметить, что прямая инструкция вращения данных отсутствует, и чтобы реализовать вращение сперва производится копирование dword в верхнюю и нижнюю часть qword, а потом производится сдвиг на право/лево. Таким образом биты из одного dword перемещаются в другой dword,что полностью идентично прямому вращению dword.

Алгоритм Компрессии позволяет вычислять только одно новое значение за раз и требует для своей работы 8 dword, учитывая что SIMD регистры уже заняты, а регистры общего назначения GPR не позволяют эффективно реализовать алгоритм Компрессии размещаем его в MMX регистрах (привет из 90-х).

В регистрах MM0-XMM3 размещаются непосредственно сами значения, а в регистрах MM4-XMM6 производятся вычисления, регистр MM7 используем для перемещения данных между SIMD и MMX. Соглашение о вызовах вообще не оговаривает состояние MMX регистров, что делает их все временными.

Скрытый текст
.code
align xmmword
Tail:
	clc
; s1
@@:	pshufw mm4,mm2,01000100b
	psrlq mm4,6
	pshufw mm5,mm4,11100100b
	psrlq mm4,5
	pxor mm5,mm4
	psrlq mm4,14
	pxor mm4,mm5
; ch
	punpckhdq mm3,mm2
	pshufw mm5,mm2,11101110b
	pand mm5,mm2
	pshufw mm6,mm2,01000100b
	pandn mm6,mm3
	pxor mm5,mm6
; t1
	paddd mm5,mm7
	psrlq mm7,20h
	paddd mm4,mm5
; d + t1
	psllq mm4,20h
	punpckldq mm2,mm1
	paddd mm2,mm4
	pshufw mm2,mm2,01001110b
; s0
	pshufw mm5,mm0,01000100b
	psrlq mm5,2
	pshufw mm6,mm5,11100100b
	psrlq mm5,11
	pxor mm6,mm5
	psrlq mm5,9
	pxor mm5,mm6	
; t1 + s0
	punpckhdq mm1,mm0
	punpckldq mm0,mm5
	paddd mm0,mm4
; maj
	pshufw mm4,mm0,01000100b
	pand mm4,mm1
	pshufw mm5,mm4,11101110b
	pxor mm4,mm5
	pshufw mm5,mm1,01001110b
	pand mm5,mm1
	pxor mm4,mm5 	; maj
; t1 + t2
	psllq mm4,20h
	paddd mm0,mm4	
	pshufw mm0,mm0,01001110b
	
	cmc
jc	@b
	add r9,08h
ret

К интересным особенностям этого алгоритма можно отнести то, что dword Hesh размещены в регистрах "змейкой", то есть меняют направление своего расположения от четного регистра к нечетному. Это позволяет эффективней получать к ним доступ и проще их перемещать.

Процедуру загрузки данных в регистры SIMD, производит разворот от big-endian к little-endian , добавление байта заглушки 80h и длины сообщения в битах в последний блок.

Скрытый текст
.code
align xmmword
Load:
	cmp rdx,0
jle	LoadPlugData

	mov eax,40h
	cmp rdx,10h
jge	LoadDataLine

ret_LoadDataLine:
	movd xmm5,eax
	mov rax,[r11]
	bt edx,3
	cmovc rax,[r11 + 8]
	mov r8,80h
	ror rax,cl
	shld r8,rax,cl
	
	xor rax,rax
	bt edx,3
	cmovc rax,r8
	cmovc r8,[r11]
	
	bswap rax
	bswap r8
	movd xmm3,r8
	movd xmm4,rax
	shufps xmm3,xmm4,00010001b
	
	movd eax,xmm5
	sub rdx,10h
	sub eax,10h
	
	cmp eax,0
jg	LoadZeroLine
	
ret_LoadZeroLine:
	pshufd xmm4,xmm3,10111011b
	movd rax,xmm4
	cmp rdx,-9
	cmovle rax,rcx
	movd xmm4,rax
	shufps xmm3,xmm4,00010100b
ret

align xmmword
@@:	pxor xmm3,xmm3
	sub rdx,10h
	sub eax,10h
jle	ret_LoadZeroLine

align xmmword
LoadZeroLine:
	movdqa xmm0,xmm1
	movdqa xmm1,xmm2
	movdqa xmm2,xmm3
	cmp rdx,0
jl	@b
	cmp rdx,10h
jl	ret_LoadDataLine
	
align xmmword
LoadDataLine:
	movdqu xmm3,xmmword ptr[r11]
	movdqa xmm4,xmm3
	psllw xmm3,8
	psrlw xmm4,8
	por xmm3,xmm4
	pshufhw xmm3,xmm3,10110001b
	pshuflw xmm3,xmm3,10110001b
	
	add r11,10h
	sub rdx,10h
	sub eax,10h
	cmp eax,0
jg	LoadZeroLine
	ret

align xmmword
LoadPlugData:
	setz al
	movzx eax,al
	shl eax,31 ; AndreyDmitriev
	movd xmm0,eax
	
	pxor xmm1,xmm1
	pxor xmm2,xmm2

	movd xmm3,rcx
	pshufd xmm3,xmm3,00011110b
	sub rdx,40h
ret

Основная процедура метода, принимает данные и запускает цикл обработки. Из интересных особенностей про него можно отметить только, что загрузка первоначального Hesh производится не из памяти а непосредственно в регистр MMX через регистры RAX.

Скрытый текст
.code
align xmmword
?ImplBin@SHA256@KILYAV@@CA?AV?$array@E$0CA@@std@@PEBDI@Z proc
?ImplBin@SHA256@KILYAV@@CA?AV?$array@E$0CA@@std@@PEBDI@Z endp
	
Bin:
	lea r9,[const]
	mov r10,rcx
	mov r11,rdx

	lea rcx,[r8 * 8]
	mov rdx,r8

	mov rax,0bb67ae856a09e667h
	movd mm0,rax
	mov rax,03c6ef372a54ff53ah ; 0a54ff53a3c6ef372h
	movd mm1,rax
	mov rax,09b05688c510e527fh
	movd mm2,rax
	mov rax,01f83d9ab5be0cd19h ; 05be0cd191f83d9abh
	movd mm3,rax
	
align xmmword
Block:
	movd qword ptr[r10 + 00h],mm0
	movd qword ptr[r10 + 08h],mm1
	movd qword ptr[r10 + 10h],mm2
	movd qword ptr[r10 + 18h],mm3
		
	call Load
	
	mov eax,18h
	align xmmword
	@@:	call Head
		dec eax
	jnz	@b
		
	mov eax,08h
	align xmmword
	@@: movdq2q mm7,xmm0
		paddd mm7,qword ptr[r9]
		paddd mm7,mm3
		
		shufps xmm0,xmm1,01001110b
		shufps xmm1,xmm2,01001110b
		shufps xmm2,xmm3,01001110b
		psrldq xmm3,8
			
		call Tail
		dec eax
	jnz	@b
	
	paddd mm0,qword ptr[r10 + 00h]
	paddd mm1,qword ptr[r10 + 08h]
	paddd mm2,qword ptr[r10 + 10h]
	paddd mm3,qword ptr[r10 + 18h]
    sub r9,100h ; AndreyDmitriev
	cmp rdx,-8
jge	Block
	
	movq2dq xmm1,mm0
	movq2dq xmm2,mm1
	call Store
	movdqa xmm0,xmm1
	movq2dq xmm1,mm2
	movq2dq xmm2,mm3
	call Store
	
	movdqu [r10 + 00h],xmm0
	movdqu [r10 + 10h],xmm1
	
	mov rax,r10
ret
	
Store:
	pshuflw xmm1,xmm1,10110001b
	pshuflw xmm2,xmm2,00011011b
	punpcklqdq xmm1,xmm2

	movdqa xmm2,xmm1
	psllw xmm1,8
	psrlw xmm2,8
	por xmm1,xmm2	
ret

Дополнительно представлена процедура Hex которая преобразует данные полученные из Bin в строку символов char.

Скрытый текст
.code
align xmmword
?ImplHex@SHA256@KILYAV@@CA?AV?$array@D$0EA@@std@@PEBDI@Z proc
?ImplHex@SHA256@KILYAV@@CA?AV?$array@D$0EA@@std@@PEBDI@Z endp

Hex:
	call Bin
	
	mov rdx,303007070909h
	movd xmm5,rdx
	punpcklbw xmm5,xmm5
	
	call HexDuoLine
	movdqu [rax + 30h],xmm2
	movdqa xmm2,xmm1
	call HexLine
	movdqu [rax + 20h],xmm2
	
	movdqa xmm1,xmm0
	call HexDuoLine
	movdqu [rax + 10h],xmm2
	movdqa xmm2,xmm1
	call HexLine
	movdqu [rax + 00h],xmm2
ret
	
align xmmword
HexDuoLine:
	movdqa xmm2,xmm1
	pxor xmm3,xmm3
	punpckhbw xmm2,xmm3
	punpcklbw xmm1,xmm3
	
align xmmword
HexLine:
	movdqa xmm3,xmm2
	psrlw xmm2,4
	psllw xmm3,12
	psrlw xmm3,4
	por xmm2,xmm3
	
	movdqa xmm3,xmm2
	pshufd xmm4,xmm5,0
	pcmpgtb xmm3,xmm4
	
	pshufd xmm4,xmm5,01010101b
	pand xmm3,xmm4
	paddb xmm2,xmm3
	
	pshufd xmm4,xmm5,10101010b
	paddb xmm2,xmm4
ret

В итоге к памяти все таки пришлось обращаться за самим сообщением, константами и сохранять начальное значение hesh блока.

Чтобы код имел хоть какай-то практический смысл, и для удобство тестирования, я скомпилировал его в статическую библиотеку под Visual Studio и написал для него С++ имплементирующий класс, который разместил в заголовочном файле.

Оставшийся часть кода не имеет принципиального значения, и/или интересных необычных приемов заслуживающих отдельного обсуждения, с ним можно ознакомится по адресу:

https://github.com/KILYAV/SHA_256

Tags:
Hubs:
Total votes 19: ↑19 and ↓0+28
Comments32

Articles