-
[C#, OpenCL] OpenCL로 SHA1 연산하기프로그래밍/C# + Unity 2023. 5. 30. 08:30728x90
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(); } } }
728x90댓글