公開鍵暗号化の署名に関してのプログラムサンプル。.Netのライブラリを使用したもの。
RSAの暗号化について調べていた時、秘密鍵を持っている人から送付した文章は、その文章がその秘密鍵を持っている人から出されたという確認はどうやってするのだろうかと疑問に思った。その時は秘密鍵で暗号化するのかなと思っていたが、それは間違いだった。
文章から秘密鍵で署名を作成し、公開鍵で署名と文書が紐づいているかを確認するということだった。
プログラムの概要
以前調べた暗号化鍵をPEM形式に変換をベースに、署名に関してのメソッドを利用。
ただし、公開鍵はPEM形式ではなくJSON形式で内部で保持するようにしている。
- 公開鍵暗号化方式で鍵を作成する。
公開鍵はJSON形式でやり取りするが、秘密鍵はオブジェクトとして保持しておく。 - 入力されたプレーンテキストから署名を作成する
- プレーンテキストと署名で、認証を行う。
実装はこんな感じにしてある。
署名・認証に関してはエラーとなるケース、今回は認証時にプレーンテキストが加工された場合と、署名が書き換えられた場合を追加してある。
サンプル実装
呼び出し側は、Verify1.CryptTest(署名する文章文字列)で呼び出す。
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text;
namespace RSATest1
{
/// <summary>
/// 認証のテスト用
/// RSAのオブジェクトは、処理単位で作成し、再利用しないようにしている。
/// これは、アプリケーションで利用する際の実装を想定している。
/// </summary>
class Verify1
{
public static void CryptTest(string plainText)
{
string publicKey;
RSAParameters privateKey;
Console.WriteLine("Original: {0}", plainText);
// 公開鍵暗号化方式でキーを作成する
// 公開キーはJSONの文字列で保存し、秘密鍵は内部パラメータで保存する
Console.WriteLine("Verify 1\n");
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) {
// 公開鍵をJSON形式にし、これをもとにキーを作成するようにする
var publicBinaryKey = rsa.ExportParameters(false);
privateKey = rsa.ExportParameters(true);
using (var stream = new MemoryStream()) {
var json = new DataContractJsonSerializer(typeof(RSAParameters));
json.WriteObject(stream, publicBinaryKey);
publicKey = new UTF8Encoding().GetString(stream.ToArray());
}
Console.WriteLine("PublicKey\n{0}\n", publicKey);
using (var stream = new MemoryStream()) {
var json = new DataContractJsonSerializer(typeof(RSAParameters));
json.WriteObject(stream, privateKey);
Console.WriteLine("PrivateKey\n{0}\n", new UTF8Encoding().GetString(stream.ToArray()));
}
}
// 署名を作成する。
var signData = SignData(plainText, privateKey);
// 署名が正しいか公開鍵を使って検証する。
Console.WriteLine("Hash Value\n{0}\n", Convert.ToBase64String(signData));
Console.WriteLine("HashCheck\n{0}\n", VerifyData(plainText, signData, publicKey));
// 文字列を加工した場合、エラーとなることを確認する。
Console.WriteLine("Hash Value\n{0}\n", Convert.ToBase64String(signData));
Console.WriteLine("HashCheck\n{0}\n", VerifyData(plainText + "1", signData, publicKey));
// 署名が改ざんされた場合、エラーとなることを確認する。
signData[0] = 0x00;
Console.WriteLine("Hash Value\n{0}\n", Convert.ToBase64String(signData));
Console.WriteLine("HashCheck\n{0}\n", VerifyData(plainText, signData, publicKey));
}
/// <summary>
/// 公開鍵で文章の署名が正しいかを確認する
/// </summary>
/// <param name="text">平文の文字列</param>
/// <param name="signData">署名データ</param>
/// <param name="publickey">JSON形式の公開鍵</param>
/// <returns>暗号化された文字列</returns>
public static bool VerifyData(string text, Byte[] signData, string publickey)
{
using (var stream = new MemoryStream(new UTF8Encoding().GetBytes(publickey))) {
var json = new DataContractJsonSerializer(typeof(RSAParameters));
if (json.ReadObject(stream) is RSAParameters work) {
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) {
rsa.ImportParameters(work);
return rsa.VerifyData(Encoding.UTF8.GetBytes(text), new SHA1CryptoServiceProvider(), signData);
}
}
}
return false;
}
/// <summary>
/// 秘密鍵で文章の署名を作成する
/// </summary>
/// <param name="text">平文の文字列</param>
/// <param name="privatekey">秘密鍵</param>
/// <returns>復号された文字列</returns>
public static byte[] SignData(string text, RSAParameters privatekey)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) {
rsa.ImportParameters(privatekey);
return rsa.SignData(Encoding.UTF8.GetBytes(text), new SHA1CryptoServiceProvider());
}
}
}
}
コメント