何を題材にするか、、、まぁいつものやつです。
こういう構成でブラウザに平文と鍵を入力して暗号文を得るって感じにするってのにgPRCを使うわけです。
画面構成はこんな感じ
1. サーバー
サーバーはgRPCやってみる(4)をベースにして、 gRPCやってみる(6)とgRPCやってみる(7)を参考にする。プロジェクトフォルダーを作成してプロジェクトを生成
mkdir rijndael-srv
cd rijndael-srv
dotnet new grpc
dotnet add package Grpc.AspNetCore.Web
mv Protos/greet.proto Protos/rijndael.proto
Protos/rijndael.proto
- syntax = "proto3";
-
- option csharp_namespace = "rijndael_srv";
-
- package rijndael_srv;
-
- //rijndaelサービス定義
- service AES128EncSrv {
- rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
- }
-
- //リクエストメッセージはbytes型の平文とbytes型の暗号鍵を含む
- message AES128EncRequest{
- bytes plain=1;
- bytes key=2;
- }
-
- //レスポンスメッセージはbytes型の暗号文
- message AES128EncReply{
- bytes encoded=1;
- }
mv Services/GreeterService.cs Services/RijndaelService.cs
Services/RijndaelService.cs
- using Google.Protobuf;
- using Grpc.Core;
- using rijndael_srv;
- using System.Runtime.InteropServices;
-
- namespace rijndael_srv.Services;
-
- public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
- {
- private readonly ILogger<AES128EncSrvService> _logger;
- public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
- {
- _logger = logger;
- }
- //[DllImport("rijndael.dll", EntryPoint="AES128Encrypt")]
- [DllImport("./librijndael.so", EntryPoint="AES128Encrypt")]
- public static extern int AES128Encrypt(IntPtr plain,IntPtr key,IntPtr ciphered);
- //[DllImport("rijndael.dll", EntryPoint="AES128Decrypt")]
- [DllImport("./librijndael.so", EntryPoint="AES128Decrypt")]
- public static extern int AES128Decrypt(IntPtr ciphered,IntPtr key,IntPtr plain);
-
- public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
- {
- int ret;
- byte[] encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
- byte[] plain=request.Plain.ToByteArray();
- byte[] key=request.Key.ToByteArray();
- int plain_size = Marshal.SizeOf(plain[0]) * plain.Length;
- string txt="";
- foreach(byte b in plain){
- txt=txt+string.Format("{0,3:X2}",b);
- }
- Console.WriteLine(txt);
- txt="";
- foreach(byte b in key){
- txt=txt+string.Format("{0,3:X2}",b);
- }
- Console.WriteLine(txt);
-
- if(plain_size<16)plain_size=16;
- IntPtr plain_intPtr = Marshal.AllocHGlobal(plain_size);
- int key_size = Marshal.SizeOf(key[0]) * key.Length;
- if(key_size<16)key_size=16;
- IntPtr key_intPtr = Marshal.AllocHGlobal(key_size);
- int encrypted_size = Marshal.SizeOf(encrypted[0]) * encrypted.Length;
- IntPtr encrypted_intPtr = Marshal.AllocHGlobal(encrypted_size);
-
- Marshal.Copy(plain, 0, plain_intPtr, plain_size);
- Marshal.Copy(key, 0, key_intPtr, key_size);
- Marshal.Copy(encrypted, 0, encrypted_intPtr, encrypted_size);
- ret=AES128Encrypt(plain_intPtr,key_intPtr,encrypted_intPtr);
- Marshal.Copy(encrypted_intPtr, encrypted, 0, encrypted.Length);
-
- txt="";
- foreach(byte b in encrypted){
- txt=txt+string.Format("{0,3:X2}",b);
- }
- Console.WriteLine(txt);
-
- return Task.FromResult(new AES128EncReply
- {
- Encoded = ByteString.CopyFrom(encrypted)
- });
- }
- }
Program.cs
- using rijndael_srv.Services;
- var builder = WebApplication.CreateBuilder(args);
-
- // gRPC-Webクライアントとの通信のために、KestrelをHTTP/1.1で動作させるよう設定します。
- builder.WebHost.ConfigureKestrel(options =>
- {
- options.ListenLocalhost(5052, o => o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1);
- });
-
- // gRPCサービスとCORSポリシーをアプリケーションに登録します。
- builder.Services.AddGrpc();
- builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
- {
- builder.AllowAnyOrigin()
- .AllowAnyMethod()
- .AllowAnyHeader()
- .WithExposedHeaders("Grpc-Status", "Grpc-Message",
- "Grpc-Encoding", "Grpc-Accept-Encoding",
- "Grpc-Status-Details-Bin");
- }));
-
- var app = builder.Build();
-
- // gRPC-Webミドルウェアを有効にします。
- app.UseGrpcWeb();
-
- // CORSミドルウェアを有効にします。
- app.UseCors();
-
- // Configure the HTTP request pipeline.
- app.MapGrpcService<AES128EncSrvService>().EnableGrpcWeb().RequireCors("AllowAll");
- app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
-
- app.Run();
よくよく見ると、HTTP/1.1を強制する部分でポートを固定化してるんだね、、、
rijndael-srv.csprojを編集(protoファイルの部分)
rijndael-srv.csproj
dotnet buildからのdotnet run
dotnet build
dotnet run
、、、うまくいった
サーバーは実行時にDLLを参照するので、サーバーのプロジェクトルートフォルダにDLL(今回はLinuxなのでlibrijndael.so)を置いておく。
rijndael-srv.csproj
- <Project Sdk="Microsoft.NET.Sdk.Web">
- <PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
- <Nullable>enable</Nullable>
- <ImplicitUsings>enable</ImplicitUsings>
- </PropertyGroup>
- <ItemGroup>
- <Protobuf Include="Protos\rijndael.proto" GrpcServices="Server" />
- </ItemGroup>
- <ItemGroup>
- <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
- <PackageReference Include="Grpc.AspNetCore.Web" Version="2.71.0" />
- </ItemGroup>
- </Project>
dotnet build
dotnet run
、、、うまくいった
サーバーは実行時にDLLを参照するので、サーバーのプロジェクトルートフォルダにDLL(今回はLinuxなのでlibrijndael.so)を置いておく。
2. クライアント
クライアントはJavaScript今さら入門シリーズの集大成!
プロジェクトフォルダーを作成
mkdir rijndael-jscli
cd rijndael-jscli
mkdir rijndael-jscli
cd rijndael-jscli
もうすでにインストールされているけど、protocとgRPC-Webプラグインをインストールする。
tool-install.sh
chmod 755 tool-install.sh
tool-install.shを実行
./tool-install.sh
tool-install.sh
- #!/bin/sh
- # プロトコルバッファコンパイラ(protoc)のインストール
- PROTOBUF_VERSION="32.1"
- wget "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
- unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc_install
- sudo mv protoc_install/bin/protoc /usr/local/bin/
- sudo mv protoc_install/include /usr/local/include/
- rm -rf protoc_install*
-
- # gRPC-Webプラグインのインストール
- GRPC_WEB_PLUGIN_VERSION="2.0.1"
- wget "https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_PLUGIN_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64"
- sudo mv "protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64" /usr/local/bin/protoc-gen-grpc-web
- sudo chmod +x /usr/local/bin/protoc-gen-grpc-web
tool-install.shに実行属性を追加chmod 755 tool-install.sh
tool-install.shを実行
./tool-install.sh
(↓これも前に(globalで)インストールしているから多分要らんけど)
protoc-gen-jsのインストール
sudo npm install -g protoc-gen-js
protoc-gen-jsのインストール
sudo npm install -g protoc-gen-js
npmパッケージのインストール
npm init -y
npm install grpc-web google-protobuf
npm init -y
npm install grpc-web google-protobuf
protoファイルの準備
mkdir Protos
cp ../rijndael-srv/Protos/* Protos
Protos/rijndael.proto
Protos/greet.protoファイルから、JavaScriptクライアントのコードを生成
生成先のディレクトリを作成
mkdir -p src/gen
protocを使ってコードを生成
-I: .protoファイルの場所を指定
--js_out: Protobufのメッセージクラスを生成
--grpc-web_out: gRPC-Webクライアントクラスを生成
protoc \
-I=./Protos \
--js_out=import_style=commonjs,binary:./src/gen \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:./src/gen \
./Protos/rijndael.proto
src/gen/rijndael_grpc_web_pb.js
と
src/gen/rijndael_pb.js
ができる
mkdir Protos
cp ../rijndael-srv/Protos/* Protos
Protos/rijndael.proto
- syntax = "proto3";
- //option csharp_namespace = "rijndael_srv";
- package rijndael_srv;
- service AES128EncSrv {
- rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
- }
- message AES128EncReply{
- bytes encoded=1;
- }
-
- message AES128EncRequest{
- bytes plain=1;
- bytes key=2;
- }
生成先のディレクトリを作成
mkdir -p src/gen
protocを使ってコードを生成
-I: .protoファイルの場所を指定
--js_out: Protobufのメッセージクラスを生成
--grpc-web_out: gRPC-Webクライアントクラスを生成
protoc \
-I=./Protos \
--js_out=import_style=commonjs,binary:./src/gen \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:./src/gen \
./Protos/rijndael.proto
src/gen/rijndael_grpc_web_pb.js
と
src/gen/rijndael_pb.js
ができる
ブラウザで開くhtmlファイルの作成(これがUIになる)
index.html
JavaScriptクライアントの実装(ここが今回のキモ部分)
src/client.js
Webpackのインストールと設定
npm install --save-dev webpack webpack-cli
webpack.config.js作成
webpack.config.js
バンドルを実行
npx webpack
index.html
- <!DOCTYPE html>
- <html lang="ja">
- <head>
- <meta charset="UTF-8">
- <title>gRPC-Web Client</title>
- </head>
- <body>
- <h1>gRPC-Web Client Example</h1>
- <p>AES128 gRPCサーバーを使って暗号文を得る</p>
- <input type="text" size=50 id="plain" placeholder="平文"><br>
- <input type="text" size=50 id="key" placeholder="鍵"><br>
- <button type="button" id="exec">実行</button><br>
- <div id="out"></div><br>
-
- <script src="dist/bundle.js"></script>
- </body>
- </html>
src/client.js
- // 生成されたコード
- const {AES128EncRequest} = require('./gen/rijndael_pb.js');
- const {AES128EncSrvClient} = require('./gen/rijndael_grpc_web_pb.js');
-
- // gRPC-Webサービスのエンドポイントを指定
- const client = new AES128EncSrvClient('http://localhost:5052', null, null);
-
- let plain=document.getElementById('plain');
- let key=document.getElementById('key');
- let out=document.getElementById('out');
- let button=document.getElementById('exec');
-
- function button_clicked(){
- const plain_txt=plain.value;
- const key_txt=key.value;
- const plain_pairs=plain_txt.match(/.{2}/g) || [];
- const key_pairs=key_txt.match(/.{2}/g) || [];
- const plain_arr=new Uint8Array(plain_pairs.map(byteString => parseInt(byteString, 16)));
- const key_arr=new Uint8Array(key_pairs.map(byteString => parseInt(byteString, 16)));
-
- // リクエストを作成
- const request = new AES128EncRequest();
- request.setPlain(plain_arr);
- request.setKey(key_arr);
-
- // AES128Encメソッドを呼び出し(関数名の先頭が勝手に小文字になる)
- client.aES128Enc(request, {}, (err, response) => {
- if (err) {
- //console.error('Error: ', err.message);
- console.error('gRPC error:', err.message, 'Details:', err.details, 'Code:', err.code);
- return;
- }
- const b_encrypted = response.getEncoded();
- // Uint8Arrayを16進数文字列に変換する
- const txt = Array.from(b_encrypted)
- .map(byte => byte.toString(16).padStart(2, '0').toUpperCase())
- .join('');
- console.log(txt);
- out.textContent=txt;
- });
- }
- button.addEventListener("click",button_clicked);
npm install --save-dev webpack webpack-cli
webpack.config.js作成
webpack.config.js
- // webpack.config.js
- const path = require('path');
- module.exports = {
- entry: './src/client.js',
- output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'bundle.js',
- },
- mode: 'development',
- resolve: {
- alias: {
- './gen': path.resolve(__dirname, 'src', 'gen')
- }
- }
- };
npx webpack
JavaScript入門は一旦おわろうかな、、、ほんと今さらだけど、まぁまぁおもしろいね(・∀・)
0 件のコメント:
コメントを投稿