성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기</h1> <p> 예전에 올렸던 소스 코드를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1300'>http://www.sysnet.pe.kr/2/0/1300</a> </pre> <br /> 개선해 보았습니다. 일단, 저 글에 공개된 소스 코드의 Main을 무한 루프로 고치고 실행하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { while (true) { FillRSA(5); } } </pre> <br /> 예외가 발생할 수 있는데, 이는 이미 <a target='tab' href='http://www.sysnet.pe.kr/2/0/1300'>1편</a>에서 글을 쓴데로 P 값보다 Q 값이 더 큰 경우에 해당합니다. 제가 웬일인지 그 부분을 주석처리했는데, 다음과 같이 주석을 해제해 주시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (q > p) { BigInteger temp = q; q = p; p = temp; } </pre> <br /> 그런데, 이렇게 해도 여전히 다음과 같은 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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}", <span style='color: blue; font-weight: bold'>ByteConverter.GetString(decryptedData)</span>); System.ArgumentNullException was unhandled HResult=-2147467261 Message=Array cannot be null. Parameter name: bytes ParamName=bytes Source=mscorlib StackTrace: at System.Text.Encoding.GetString(Byte[] bytes) at producePrime.Program.FillRSA(Int32 certainty) in E:\...\producePrime\Program.cs:line 159 at producePrime.Program.Main(String[] args) in E:\...\producePrime\Program.cs:line 16 InnerException: </pre> <br /> 결과는 null이지만, RSADecrypt에서 예외가 발생했기 때문에 decryptedData에 null이 들어간 것입니다. 결국 최초의 예외는 다음에서 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>System.Security.Cryptography.CryptographicException: The parameter is incorrect.</span> at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) at System.Security.Cryptography.RSACryptoServiceProvider.DecryptKey(SafeKeyHandle pKeyContext, Byte[] pbEncryptedKey, Int32 cbEncryptedKey, Boolean fOAEP, ObjectHandleOnStack ohRetDecryptedKey) at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP) at producePrime.Program.RSADecrypt(Byte[] DataToDecrypt, RSAParameters RSAKeyInfo, Boolean DoOAEPPadding) in E:\...\producePrime\Program.cs:line 309 </pre> <br /> 이에 대한 원인이 재미있는데요. RSAParameters.Modulus의 첫 번째 바이트에 0이 들어갔기 때문입니다. 즉, P * Q의 값이 충분히 크지 않게 되면 발생하는 것인데, 가령 P와 Q의 값이 각각 64바이트 길이인데, 그 곱하기 값인 Modulus가 128바이트를 모두 채우지 않는 경우가 되면 (암호화 메서드인 Encrypt 호출은 통과하지만) 복호화 메서드인 Decrypt 호출 시 "The parameter is incorrect" 예외가 발생하는 것입니다.<br /> <br /> 따라서, 이런 조건을 갖는 P, Q의 키 값이 생성된 경우에는 그냥 버리고 다시 P, Q를 생성해야 합니다. 이렇게 해서 안정적인 버전의 소스 코드를 만들면 다음과 같습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Text; using System.Security.Cryptography; using System.Numerics; using System.Diagnostics; namespace producePrime { class Program { static void Main(string[] args) { while (true) { FillRSA(5); } } static void FillRSA(int certainty) { int modulusSize = 1024; int modulusByteSize = modulusSize / 8 / 2; RSAParameters rsaParam = new RSAParameters(); // P, Q 값 생성하고, BigInteger p, q; Stopwatch st = new Stopwatch(); st.Start(); ProducePQ(modulusByteSize, out p, certainty); ProducePQ(modulusByteSize, out q, certainty); st.Stop(); Console.WriteLine("Elapsed: " + st.ElapsedMilliseconds + "(ms)"); <span style='color: blue; font-weight: bold'>if (q > p) { BigInteger temp = q; q = p; p = temp; }</span> // N 값 구하고, BigInteger n = p * q; // E 값 생성하고, int publicExponent = 65537; // Φ 값 구하고, BigInteger rp = (p - 1) * (q - 1); // D 값 구하고, BigInteger d = GetExtendedGcd(publicExponent, rp).Item2; if (d < 0) { BigInteger tMultiplier = BigInteger.Min(publicExponent, rp); BigInteger tDivisor = BigInteger.Max(publicExponent, rp); d = tDivisor + d; Console.WriteLine("d < 0"); Console.WriteLine(); BigInteger dMod = BigInteger.Remainder(d * tMultiplier, tDivisor); if (dMod.IsOne == false) { Console.WriteLine("[Failed] d * e mod rp == " + dMod); Console.WriteLine(); return; } } // InverseQ 값 구하고, BigInteger inverseQ = GetExtendedGcd(q, p).Item2; if (inverseQ < 0) { BigInteger tMultiplier = BigInteger.Min(q, p); BigInteger tDivisor = BigInteger.Max(q, p); inverseQ = tDivisor + inverseQ; Console.WriteLine("inverseQ < 0"); Console.WriteLine(); BigInteger qMod = BigInteger.Remainder(inverseQ * tMultiplier, tDivisor); if (qMod.IsOne == false) { Console.WriteLine("[Failed] inverseQ * q mod p == " + qMod); Console.WriteLine(); return; } } // DP, DQ 값 구하고, BigInteger dp = BigInteger.Remainder(d, (p - 1)); BigInteger dq = BigInteger.Remainder(d, (q - 1)); ToBigEndian(p, ref rsaParam.P, modulusByteSize); ToBigEndian(q, ref rsaParam.Q, modulusByteSize); ToBigEndian(n, ref rsaParam.Modulus, modulusByteSize * 2); <span style='color: blue; font-weight: bold'>if (rsaParam.Modulus[0] == 0) { return; } </span> ToBigEndian(publicExponent, ref rsaParam.Exponent, 3); ToBigEndian(d, ref rsaParam.D, modulusByteSize * 2); ToBigEndian(dp, ref rsaParam.DP, modulusByteSize); ToBigEndian(dq, ref rsaParam.DQ, modulusByteSize); ToBigEndian(inverseQ, ref rsaParam.InverseQ, modulusByteSize); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.ImportParameters(rsaParam); // 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)); } <span style='color: blue; font-weight: bold'>private static void ToBigEndian(BigInteger bytes, ref byte[] target, int byteSize) { List<byte> list = new List<byte>(); list.AddRange(bytes.ToByteArray()); for (int i = list.Count; i < byteSize; i ++) { list.Add(0); } list.Reverse(); target = new byte[list.Count]; Array.Copy(list.ToArray(), target, list.Count); }</span> private static Tuple<BigInteger, BigInteger> GetExtendedGcd(BigInteger num1, BigInteger num2) { if (num2 > num1) { return GetExtendedGcd(num2, num1); } BigInteger x = 0; BigInteger lastx = 1; BigInteger y = 1; BigInteger lasty = 0; BigInteger quotient = 0; BigInteger tempNum2, tempx, tempy; while (num2 != 0) { quotient = num1 / num2; tempNum2 = num2; num2 = num1 % num2; num1 = tempNum2; tempx = lastx - quotient * x; lastx = x; x = tempx; tempy = lasty - quotient * y; lasty = y; y = tempy; } return new Tuple<BigInteger, BigInteger>(lastx, lasty); } private static void ProducePQ(int bytes, out BigInteger p, int certainty) { Random rand = new Random(); byte[] pRand = new byte[bytes]; while (true) { rand.NextBytes(pRand); p = new BigInteger(pRand); if (p.IsProbablePrime(certainty) == true) { break; } } } public static bool IsPrime(BigInteger candidate) { if ((candidate & 1) == 0) { if (candidate == 2) { return true; } else { return false; } } for (BigInteger i = 3; (i * i) <= candidate; i += 2) { if ((candidate % i) == 0) { return false; } } return candidate != 1; } static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding) { try { byte[] encryptedData; using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider()) { RSA.ImportParameters(RSAKeyInfo); encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding); } return encryptedData; } catch (CryptographicException e) { Console.WriteLine(e.Message); return null; } } static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding) { try { byte[] decryptedData; using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider()) { RSA.ImportParameters(RSAKeyInfo); decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding); } return decryptedData; } catch (CryptographicException e) { Console.WriteLine(e.ToString()); return null; } } } } </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1003&boardid=331301885'>첨부된 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1259
(왼쪽의 숫자를 입력해야 합니다.)