[C#, OpenCL] OpenCL로 SHA1 연산하기
카테고리: C# + Unity
Nuget
C#에서 OpenCL을 사용하고자 할 때, 여러 방법이 있지만 보통 Nuget Package에서 Cloo를 추가하여 사용한다.
1. kernelSource 작성
본격적으로 OpenCL을 사용하기 이전에, 아래와 같이 kernelSource를 작성해야한다.
마땅히 참고할 소스코드가 없어, https://en.wikipedia.org/wiki/SHA-1 의 pseudocode를 참조하여 구현하였다.
__kernel void sha1(__global const unsigned char* data, int length, __global uint* digest) {
int num_blocks = length / 512;
uint h0 = 0x67452301;
uint h1 = 0xEFCDAB89;
uint h2 = 0x98BADCFE;
uint h3 = 0x10325476;
uint h4 = 0xC3D2E1F0;
uint w[80];
for (int i = 0; i < num_blocks; i++) {
for (int j = 0; j < 16; j++) {
w[j] = data[i * 64 + j * 4] << 24;
w[j] |= data[i * 64 + j * 4 + 1] << 16;
w[j] |= data[i * 64 + j * 4 + 2] << 8;
w[j] |= data[i * 64 + j * 4 + 3];
}
for (int j = 16; j < 80; j++) {
uint temp = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16];
w[j] = ((temp << 1) | (temp >> 31));
}
uint a = h0;
uint b = h1;
uint c = h2;
uint d = h3;
uint e = h4;
for (int j = 0; j < 80; j++) {
uint f, k;
if (j < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (j < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (j < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint temp = ((a << 5) | (a >> 27)) + f + e + k + w[j];
e = d;
d = c;
c = ((b << 30) | (b >> 2));
b = a;
a = temp;
}
h0 += a;
h1 += b;
h2 += c;
h3 += d;
h4 += e;
}
digest[0] = h0;
digest[1] = h1;
digest[2] = h2;
digest[3] = h3;
digest[4] = h4;
}
2. Padding 함수 구현
SHA1에서는 기본적으로 512비트 단위의 패딩을 요구하므로, RFC 3174에 맞게 아래와 같이 구현하였다.
static byte[] PadMessage(byte[] message) {
// 원본 메시지 길이 (비트 단위)
long originalLengthBits = (long)message.Length * 8;
int padBytesLength;
// 메시지의 길이가 64바이트의 배수가 되도록 패딩 길이 계산
if (message.Length % 64 < 56) {
padBytesLength = 56 - (message.Length % 64);
} else {
padBytesLength = 120 - (message.Length % 64);
}
// 추가적인 8바이트는 원본 메시지 길이를 저장하기 위함
byte[] padded = new byte[message.Length + padBytesLength + 8];
// 원본 메시지 복사
Array.Copy(message, 0, padded, 0, message.Length);
// 1 추가
padded[message.Length] = 0x80;
// 원본 길이를 이진 형태로 추가
// for loop를 역순으로 실행하여 big endian bit order로 저장
for (int i = 0; i < 8; i++) {
padded[padded.Length - 1 - i] = (byte)(originalLengthBits >> (i * 8));
}
return padded;
}
3. 전체 코드 구현
using System.Text;
using Cloo;
namespace OpenCLSHA1 {
public class OpenCL_SHA1 {
private readonly ComputeKernel kernel;
private readonly ComputeContext context;
private readonly ComputeCommandQueue queue;
static string kernelSource = @" 상단 kernelSource 입력 ";
public OpenCL_SHA1() {
ComputeProgram? program = null;
try {
ComputePlatform platform = ComputePlatform.Platforms[0];
ComputeDevice device = platform.Devices[0];
context = new ComputeContext(new[] { device }, new ComputeContextPropertyList(platform), null, IntPtr.Zero);
queue = new ComputeCommandQueue(context, context.Devices[0], ComputeCommandQueueFlags.None);
program = new ComputeProgram(context, kernelSource);
program.Build(new[] { device }, null, null, IntPtr.Zero);
kernel = program.CreateKernel("sha1");
} catch (BuildProgramFailureComputeException) {
if (program != null) {
Console.WriteLine("Error building program: " + program.GetBuildLog(program.Devices[0]));
}
throw;
} catch (Exception e) {
Console.WriteLine("Error building program: " + e.Message);
throw;
}
}
~OpenCL_SHA1() {
kernel.Dispose();
queue.Dispose();
context.Dispose();
}
public string Compute(string input) {
byte[] message = PadMessage(Encoding.ASCII.GetBytes(input));
uint[] digest = new uint[5];
using ComputeBuffer<byte> messageBuffer = new(context, ComputeMemoryFlags.ReadWrite, message.Length);
using ComputeBuffer<uint> digestBuffer = new(context, ComputeMemoryFlags.WriteOnly, digest.Length);
kernel.SetMemoryArgument(0, messageBuffer);
kernel.SetValueArgument(1, message.Length * 8);
kernel.SetMemoryArgument(2, digestBuffer);
queue.Execute(kernel, null, new long[] { 1 }, null, null);
queue.ReadFromBuffer(digestBuffer, ref digest, true, null);
StringBuilder sb = new StringBuilder();
foreach (uint i in digest) {
sb.Append(i.ToString("x8"));
}
return sb.ToString();
}
}
}