카테고리:

9 분 소요

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();
        }
    }
}

태그: Cloo, ComputeProgram, Digest, kernel, kernelSource, Nuget, OpenCL, padding, SHA-1, sha1

업데이트: