Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 10개 있습니다.)
.NET Framework: 292. RSACryptoServiceProvider의 공개키와 개인키 구분
; https://www.sysnet.pe.kr/2/0/1218

.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기
; https://www.sysnet.pe.kr/2/0/1295

.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider 의 개인키를 직접 채워보자
; https://www.sysnet.pe.kr/2/0/1300

.NET Framework: 356. (공개키를 담은) 자바의 key 파일을 닷넷의 RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1401

.NET Framework: 383. RSAParameters의 ToXmlString과 ExportParameters의 결과 비교
; https://www.sysnet.pe.kr/2/0/1491

.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10925

.NET Framework: 566. openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/10926

.NET Framework: 638. RSAParameters와 RSA
; https://www.sysnet.pe.kr/2/0/11140

.NET Framework: 1037. openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)
; https://www.sysnet.pe.kr/2/0/12598

.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법
; https://www.sysnet.pe.kr/2/0/13245




openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)

예전에 소개한 글에서 BouncyCastle을 이용한 방법을 다뤘는데요,

openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/10926

순수하게 key file을 분석한 소스 코드가 있어 소개합니다. ^^

How to load the RSA public key from file in C#
; https://stackoverflow.com/questions/11506891/how-to-load-the-rsa-public-key-from-file-in-c-sharp/32243171#32243171

위의 덧글에 보면, BEGIN/END PUBLIC KEY, BEGIN/END PRIVATE KEY, BEGIN/END ENCRYPTED PRIVATE KEY에 대한 RSACryptoServiceProvider 변환을 해주는 소스 코드를 담고 있습니다.

// Decoding an SSH Key from PEM to BASE64 to HEX to ASN.1 to prime decimal numbers
// ; http://feeds.hanselman.com/~/566255904/0/scotthanselman~Decoding-an-SSH-Key-from-PEM-to-BASE-to-HEX-to-ASN-to-prime-decimal-numbers.aspx

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;

public class PemKeyUtils
{
    const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
    const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
    const String pempubheader = "-----BEGIN PUBLIC KEY-----";
    const String pempubfooter = "-----END PUBLIC KEY-----";
    const String pemp8header = "-----BEGIN PRIVATE KEY-----";
    const String pemp8footer = "-----END PRIVATE KEY-----";
    const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
    const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----";

    static bool verbose = false;

    public static RSACryptoServiceProvider GetRSAProviderFromPemFile(String pemfile)
    {
        bool isPrivateKeyFile = true;
        string pemstr = File.ReadAllText(pemfile).Trim();
        if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
        {
            isPrivateKeyFile = false;
        }

        return GetRSAProviderFromPem(pemfile, isPrivateKeyFile);
    }

    public static RSACryptoServiceProvider GetRSAProviderFromPem(String pemText, bool isPrivate)
    { 
        byte[] pemkey;
        if (isPrivate)
            pemkey = DecodeOpenSSLPrivateKey(pemText);
        else
            pemkey = DecodeOpenSSLPublicKey(pemText);

        if (pemkey == null)
            return null;

        if (isPrivate)
            return DecodeRSAPrivateKey(pemkey);
        else
            return DecodeX509PublicKey(pemkey);
    }

    //------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider  ---
    static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
    {
        byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

        // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
        MemoryStream mem = new MemoryStream(privkey);
        BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
        byte bt = 0;
        ushort twobytes = 0;
        int elems = 0;
        try
        {
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102) //version number
                return null;
            bt = binr.ReadByte();
            if (bt != 0x00)
                return null;


            //------  all private key components are Integer sequences ----
            elems = GetIntegerSize(binr);
            MODULUS = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            E = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            D = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            P = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            Q = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DP = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DQ = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            IQ = binr.ReadBytes(elems);

            if (verbose)
            {
                Console.WriteLine("showing components ..");
                showBytes("\nModulus", MODULUS);
                showBytes("\nExponent", E);
                showBytes("\nD", D);
                showBytes("\nP", P);
                showBytes("\nQ", Q);
                showBytes("\nDP", DP);
                showBytes("\nDQ", DQ);
                showBytes("\nIQ", IQ);
            }

            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSAParameters RSAparams = new RSAParameters();
            RSAparams.Modulus = MODULUS;
            RSAparams.Exponent = E;
            RSAparams.D = D;
            RSAparams.P = P;
            RSAparams.Q = Q;
            RSAparams.DP = DP;
            RSAparams.DQ = DQ;
            RSAparams.InverseQ = IQ;
            RSA.ImportParameters(RSAparams);
            return RSA;
        }
        catch (Exception)
        {
            return null;
        }
        finally { binr.Close(); }
    }

    //--------   Get the binary RSA PUBLIC key   --------
    static byte[] DecodeOpenSSLPublicKey(String instr)
    {
        const String pempubheader = "-----BEGIN PUBLIC KEY-----";
        const String pempubfooter = "-----END PUBLIC KEY-----";
        String pemstr = instr.Trim();
        byte[] binkey;
        if (!pemstr.StartsWith(pempubheader) || !pemstr.EndsWith(pempubfooter))
            return null;
        StringBuilder sb = new StringBuilder(pemstr);
        sb.Replace(pempubheader, "");  //remove headers/footers, if present
        sb.Replace(pempubfooter, "");

        String pubstr = sb.ToString().Trim();   //get string after removing leading/trailing whitespace

        try
        {
            binkey = Convert.FromBase64String(pubstr);
        }
        catch (System.FormatException)
        {       //if can't b64 decode, data is not valid
            return null;
        }
        return binkey;
    }

    //-----  Get the binary RSA PRIVATE key, decrypting if necessary ----
    static byte[] DecodeOpenSSLPrivateKey(String instr)
    {
        const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
        const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
        String pemstr = instr.Trim();
        byte[] binkey;
        if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
            return null;

        StringBuilder sb = new StringBuilder(pemstr);
        sb.Replace(pemprivheader, "");  //remove headers/footers, if present
        sb.Replace(pemprivfooter, "");

        String pvkstr = sb.ToString().Trim();   //get string after removing leading/trailing whitespace

        try
        {        // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
            binkey = Convert.FromBase64String(pvkstr);
            return binkey;
        }
        catch (System.FormatException)
        {       //if can't b64 decode, it must be an encrypted private key
                //Console.WriteLine("Not an unencrypted OpenSSL PEM private key");  
        }

        StringReader str = new StringReader(pvkstr);

        //-------- read PEM encryption info. lines and extract salt -----
        if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
            return null;
        String saltline = str.ReadLine();
        if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
            return null;
        String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
        byte[] salt = new byte[saltstr.Length / 2];
        for (int i = 0; i < salt.Length; i++)
            salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
        if (!(str.ReadLine() == ""))
            return null;

        //------ remaining b64 data is encrypted RSA key ----
        String encryptedstr = str.ReadToEnd();

        try
        {   //should have b64 encrypted RSA key now
            binkey = Convert.FromBase64String(encryptedstr);
        }
        catch (System.FormatException)
        {  // bad b64 data.
            return null;
        }

        //------ Get the 3DES 24 byte key using PDK used by OpenSSL ----

        SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>");
        //Console.Write("\nEnter password to derive 3DES key: ");
        //String pswd = Console.ReadLine();
        byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2);    // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
        if (deskey == null)
            return null;
        //showBytes("3DES key", deskey) ;

        //------ Decrypt the encrypted 3des-encrypted RSA private key ------
        byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
        if (rsakey != null)
            return rsakey;  //we have a decrypted RSA private key
        else
        {
            Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
            return null;
        }
    }

    static SecureString GetSecPswd(String prompt)
    {
        SecureString password = new SecureString();

        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write(prompt);
        Console.ForegroundColor = ConsoleColor.Magenta;

        while (true)
        {
            ConsoleKeyInfo cki = Console.ReadKey(true);
            if (cki.Key == ConsoleKey.Enter)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine();
                return password;
            }
            else if (cki.Key == ConsoleKey.Backspace)
            {
                // remove the last asterisk from the screen...
                if (password.Length > 0)
                {
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    Console.Write(" ");
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    password.RemoveAt(password.Length - 1);
                }
            }
            else if (cki.Key == ConsoleKey.Escape)
            {
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine();
                return password;
            }
            else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
            {
                if (password.Length < 20)
                {
                    password.AppendChar(cki.KeyChar);
                    Console.Write("*");
                }
                else
                {
                    Console.Beep();
                }
            }
            else
            {
                Console.Beep();
            }
        }
    }

    static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
    {
        MemoryStream memst = new MemoryStream();
        TripleDES alg = TripleDES.Create();
        alg.Key = desKey;
        alg.IV = IV;
        try
        {
            CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
            cs.Write(cipherData, 0, cipherData.Length);
            cs.Close();
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc.Message);
            return null;
        }
        byte[] decryptedData = memst.ToArray();
        return decryptedData;
    }

    //-----   OpenSSL PBKD uses only one hash cycle (count); miter is number of iterations required to build sufficient bytes ---
    static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter)
    {
        IntPtr unmanagedPswd = IntPtr.Zero;
        int HASHLENGTH = 16;    //MD5 bytes
        byte[] keymaterial = new byte[HASHLENGTH * miter];     //to store contatenated Mi hashed results


        byte[] psbytes = new byte[secpswd.Length];
        unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
        Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
        Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);

        //UTF8Encoding utf8 = new UTF8Encoding();
        //byte[] psbytes = utf8.GetBytes(pswd);

        // --- contatenate salt and pswd bytes into fixed data array ---
        byte[] data00 = new byte[psbytes.Length + salt.Length];
        Array.Copy(psbytes, data00, psbytes.Length);      //copy the pswd bytes
        Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes

        // ---- do multi-hashing and contatenate results  D1, D2 ...  into keymaterial bytes ----
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] result = null;
        byte[] hashtarget = new byte[HASHLENGTH + data00.Length];   //fixed length initial hashtarget

        for (int j = 0; j < miter; j++)
        {
            // ----  Now hash consecutively for count times ------
            if (j == 0)
                result = data00;    //initialize 
            else
            {
                Array.Copy(result, hashtarget, result.Length);
                Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
                result = hashtarget;
                //Console.WriteLine("Updated new initial hash target:") ;
                //showBytes(result) ;
            }

            for (int i = 0; i < count; i++)
                result = md5.ComputeHash(result);
            Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length);  //contatenate to keymaterial
        }
        //showBytes("Final key material", keymaterial);
        byte[] deskey = new byte[24];
        Array.Copy(keymaterial, deskey, deskey.Length);

        Array.Clear(psbytes, 0, psbytes.Length);
        Array.Clear(data00, 0, data00.Length);
        Array.Clear(result, 0, result.Length);
        Array.Clear(hashtarget, 0, hashtarget.Length);
        Array.Clear(keymaterial, 0, keymaterial.Length);

        return deskey;
    }

    public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
    {
        // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        byte[] seq = new byte[15];
        // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
        MemoryStream mem = new MemoryStream(x509key);
        BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
        byte bt = 0;
        ushort twobytes = 0;

        try
        {

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            seq = binr.ReadBytes(15);       //read the Sequence OID
            if (!CompareBytearrays(seq, SeqOID))    //make sure Sequence for OID is correct
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8203)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            bt = binr.ReadByte();
            if (bt != 0x00)     //expect null byte next
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte();    //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16();   //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            byte lowbyte = 0x00;
            byte highbyte = 0x00;

            if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
            else if (twobytes == 0x8202)
            {
                highbyte = binr.ReadByte(); //advance 2 bytes
                lowbyte = binr.ReadByte();
            }
            else
                return null;
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
            int modsize = BitConverter.ToInt32(modint, 0);

            byte firstbyte = binr.ReadByte();
            binr.BaseStream.Seek(-1, SeekOrigin.Current);

            if (firstbyte == 0x00)
            {   //if first byte (highest order) of modulus is zero, don't include it
                binr.ReadByte();    //skip this null byte
                modsize -= 1;   //reduce modulus buffer size by 1
            }

            byte[] modulus = binr.ReadBytes(modsize);   //read the modulus bytes

            if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
                return null;
            int expbytes = (int)binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
            byte[] exponent = binr.ReadBytes(expbytes);

            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSAParameters RSAKeyInfo = new RSAParameters();
            RSAKeyInfo.Modulus = modulus;
            RSAKeyInfo.Exponent = exponent;
            RSA.ImportParameters(RSAKeyInfo);
            return RSA;
        }
        catch (Exception)
        {
            return null;
        }

        finally { binr.Close(); }

    }

    static void showBytes(String info, byte[] data)
    {
        Console.WriteLine("{0}  [{1} bytes]", info, data.Length);
        for (int i = 1; i <= data.Length; i++)
        {
            Console.Write("{0:X2}  ", data[i - 1]);
            if (i % 16 == 0)
                Console.WriteLine();
        }
        Console.WriteLine("\n\n");
    }

    private static int GetIntegerSize(BinaryReader binr)
    {
        byte bt = 0;
        byte lowbyte = 0x00;
        byte highbyte = 0x00;
        int count = 0;
        bt = binr.ReadByte();
        if (bt != 0x02)     //expect integer
            return 0;
        bt = binr.ReadByte();

        if (bt == 0x81)
            count = binr.ReadByte();    // data size in next byte
        else
            if (bt == 0x82)
        {
            highbyte = binr.ReadByte(); // data size in next 2 bytes
            lowbyte = binr.ReadByte();
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
            count = BitConverter.ToInt32(modint, 0);
        }
        else
        {
            count = bt;     // we already have the data size
        }

        while (binr.ReadByte() == 0x00)
        {   //remove high order zeros in data
            count -= 1;
        }
        binr.BaseStream.Seek(-1, SeekOrigin.Current);     //last ReadByte wasn't a removed zero, so back up a byte
        return count;
    }

    static bool CompareBytearrays(byte[] a, byte[] b)
    {
        if (a.Length != b.Length)
            return false;
        int i = 0;
        foreach (byte c in a)
        {
            if (c != b[i])
                return false;
            i++;
        }
        return true;
    }
}

위의 소스 코드를 활용하면 BouncyCastle 의존성 없이 이런 식으로 RSACryptoServiceProvider를 사용할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        string userKey = @"-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEAz849b+b4pKd2Gk34NOCPF+zuO9jG6E+pL+ZXzUFAXJfh1fGB
...[생략]...
1JzXJK0bWJ4p81eTEq6SFrmZVX0ZR/5M6wQ/x2WsKszZQ/OIoLO9mCKrDnsd/w==
-----END RSA PRIVATE KEY-----";

        RSACryptoServiceProvider rsa = PemKeyUtils.GetRSAProviderFromPem(userKey, isPrivate: true);

        // RSACryptoServiceProvider로 암/복호화 테스트
        UnicodeEncoding ByteConverter = new UnicodeEncoding();

        byte[] dataToEncrypt = ByteConverter.GetBytes("Data to Encrypt");
        byte[] encryptedData;
        byte[] decryptedData;

        encryptedData = RSAEncrypt(dataToEncrypt, rsa.ExportParameters(false), false);
        decryptedData = RSADecrypt(encryptedData, rsa.ExportParameters(true), false);

        Console.WriteLine("Decrypted plaintext: {0}", ByteConverter.GetString(decryptedData)); 
/* 출력 결과
Decrypted plaintext: Data to Encrypt
*/
    }

    static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
    {
        // ...[생략]... (참고: https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec)
    }

    static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
    {
        // ...[생략]... (참고: https://www.sysnet.pe.kr/2/0/10926#rsa_enc_dec)
    }
}

해본 김에, Kubernetes의 kubeconfig 파일과도 이야기를 엮어볼까요? ^^

즉, k8s 설치 시 kubeconfig 파일에 담긴 client-certificate-data 개인키 문자열로부터 RSACryptoServiceProvider를 초기화하는 코드를 다음과 같이 만들 수 있습니다.

using k8s;

// Install-Package KubernetesClient

string path = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%\.kube\config");
KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(path);

string userKey = Encoding.UTF8.GetString(Convert.FromBase64String(config.ClientCertificateKeyData));
RSACryptoServiceProvider rsa = PemKeyUtils.GetRSAProviderFromPem(userKey, true);

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/14/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13420정성태9/26/20235240스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233087스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20233748닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233332닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
13416정성태9/19/20233150오류 유형: 877. redis-py - MISCONF Redis is configured to save RDB snapshots, ...
13415정성태9/18/20233635닷넷: 2144. C# 12 - 컬렉션 식(Collection Expressions)
13414정성태9/16/20233385디버깅 기술: 193. Windbg - ThreadStatic 필드 값을 조사하는 방법
13413정성태9/14/20233575닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20236843닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233342Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20234855닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233700닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233707Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
13407정성태9/4/20233468닷넷: 2139. C# - ParallelEnumerable을 이용한 IEnumerable에 대한 병렬 처리
13406정성태9/4/20233400VS.NET IDE: 186. Visual Studio Community 버전의 라이선스
13405정성태9/3/20233819닷넷: 2138. C# - async 메서드 호출 원칙
13404정성태8/29/20233355오류 유형: 876. Windows - 키보드의 등호(=, Equals sign) 키가 눌리지 않는 경우
13403정성태8/21/20233186오류 유형: 875. The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EB3E94ADBE1229CF
13402정성태8/20/20233243닷넷: 2137. ILSpy의 nuget 라이브러리 버전 - ICSharpCode.Decompiler
13401정성태8/19/20233505닷넷: 2136. .NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성 [1]
13400정성태8/10/20233341오류 유형: 874. 파이썬 - pymssql을 윈도우 환경에서 설치 불가
13399정성태8/9/20233368닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234117스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
13397정성태7/23/20233634닷넷: 2134. C# - 문자열 연결 시 string.Create를 이용한 GC 할당 최소화
13396정성태7/22/20233330스크립트: 54. 파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거
13395정성태7/20/20233304개발 환경 구성: 685. 로컬에서 개발 중인 ASP.NET Core/5+ 웹 사이트에 대해 localhost 이외의 호스트 이름으로 접근하는 방법
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...