• [C#, OpenCL] OpenCL로 SHA1 연산하기
    프로그래밍/C# + Unity 2023. 5. 30. 08:30
    728x90

    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

    댓글

Copyright ⓒ syudal.tistory.com