2025年9月20日土曜日

JavaScript今さら入門(6)

今さらなんだけど、JavaScriptに挑戦してみるシリーズ。JavaScriptでgRPC Clientを作ってみる試み。Greeterから卒業。JavaScriptからDLLを呼び出せてるように見せるのを目指す。

何を題材にするか、、、まぁいつものやつです。
こういう構成でブラウザに平文と鍵を入力して暗号文を得るって感じにするってのにgPRCを使うわけです。
画面構成はこんな感じ

1. サーバー
サーバーはgRPCやってみる(4)をベースにして、 gRPCやってみる(6)gRPCやってみる(7)を参考にする。
プロジェクトフォルダーを作成してプロジェクトを生成
mkdir rijndael-srv
cd rijndael-srv
dotnet new grpc

Grpc.AspNetCore.Web パッケージへの参照を追加
dotnet add package Grpc.AspNetCore.Web

protoファイルをリネームして編集
mv Protos/greet.proto Protos/rijndael.proto
Protos/rijndael.proto
  1. syntax = "proto3";
  2.  
  3. option csharp_namespace = "rijndael_srv";
  4.  
  5. package rijndael_srv;
  6.  
  7. //rijndaelサービス定義
  8. service AES128EncSrv {
  9.   rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  10. }
  11.  
  12. //リクエストメッセージはbytes型の平文とbytes型の暗号鍵を含む
  13. message AES128EncRequest{
  14.   bytes plain=1;
  15.   bytes key=2;
  16. }
  17.  
  18. //レスポンスメッセージはbytes型の暗号文
  19. message AES128EncReply{
  20.   bytes encoded=1;
  21. }

サービス(処理の実態)をリネームして編集
mv Services/GreeterService.cs Services/RijndaelService.cs
Services/RijndaelService.cs
  1. using Google.Protobuf;
  2. using Grpc.Core;
  3. using rijndael_srv;
  4. using System.Runtime.InteropServices;
  5.  
  6. namespace rijndael_srv.Services;
  7.  
  8. public class AES128EncSrvService : AES128EncSrv.AES128EncSrvBase
  9. {
  10.     private readonly ILogger<AES128EncSrvService> _logger;
  11.     public AES128EncSrvService(ILogger<AES128EncSrvService> logger)
  12.     {
  13.         _logger = logger;
  14.     }
  15.     //[DllImport("rijndael.dll", EntryPoint="AES128Encrypt")]
  16.     [DllImport("./librijndael.so", EntryPoint="AES128Encrypt")]
  17.     public static extern int AES128Encrypt(IntPtr plain,IntPtr key,IntPtr ciphered);
  18.     //[DllImport("rijndael.dll", EntryPoint="AES128Decrypt")]
  19.     [DllImport("./librijndael.so", EntryPoint="AES128Decrypt")]
  20.     public static extern int AES128Decrypt(IntPtr ciphered,IntPtr key,IntPtr plain);
  21.     
  22.     public override Task<AES128EncReply> AES128Enc(AES128EncRequest request, ServerCallContext context)
  23.     {
  24.         int ret;
  25.         byte[] encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
  26.         byte[] plain=request.Plain.ToByteArray();
  27.         byte[] key=request.Key.ToByteArray();
  28.         int plain_size = Marshal.SizeOf(plain[0]) * plain.Length;
  29.         string txt="";
  30.         foreach(byte b in plain){
  31.             txt=txt+string.Format("{0,3:X2}",b);
  32.         }
  33.         Console.WriteLine(txt);
  34.         txt="";
  35.         foreach(byte b in key){
  36.             txt=txt+string.Format("{0,3:X2}",b);
  37.         }
  38.         Console.WriteLine(txt);
  39.  
  40.         if(plain_size<16)plain_size=16;
  41.         IntPtr plain_intPtr = Marshal.AllocHGlobal(plain_size);
  42.         int key_size = Marshal.SizeOf(key[0]) * key.Length;
  43.         if(key_size<16)key_size=16;
  44.         IntPtr key_intPtr = Marshal.AllocHGlobal(key_size);
  45.         int encrypted_size = Marshal.SizeOf(encrypted[0]) * encrypted.Length;
  46.         IntPtr encrypted_intPtr = Marshal.AllocHGlobal(encrypted_size);
  47.  
  48.         Marshal.Copy(plain, 0, plain_intPtr, plain_size);
  49.                 Marshal.Copy(key, 0, key_intPtr, key_size);
  50.         Marshal.Copy(encrypted, 0, encrypted_intPtr, encrypted_size);
  51.         ret=AES128Encrypt(plain_intPtr,key_intPtr,encrypted_intPtr);
  52.         Marshal.Copy(encrypted_intPtr, encrypted, 0, encrypted.Length);
  53.  
  54.         txt="";
  55.         foreach(byte b in encrypted){
  56.             txt=txt+string.Format("{0,3:X2}",b);
  57.         }
  58.         Console.WriteLine(txt);
  59.  
  60.         return Task.FromResult(new AES128EncReply
  61.         {
  62.             Encoded = ByteString.CopyFrom(encrypted)
  63.         });
  64.     }
  65. }

Program.csを編集
Program.cs
  1. using rijndael_srv.Services;
  2. var builder = WebApplication.CreateBuilder(args);
  3.  
  4. // gRPC-Webクライアントとの通信のために、KestrelをHTTP/1.1で動作させるよう設定します。
  5. builder.WebHost.ConfigureKestrel(options =>
  6. {
  7.     options.ListenLocalhost(5052, o => o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1);
  8. });
  9.  
  10. // gRPCサービスとCORSポリシーをアプリケーションに登録します。
  11. builder.Services.AddGrpc();
  12. builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
  13. {
  14.     builder.AllowAnyOrigin()
  15.             .AllowAnyMethod()
  16.             .AllowAnyHeader()
  17.             .WithExposedHeaders("Grpc-Status", "Grpc-Message",
  18.                 "Grpc-Encoding", "Grpc-Accept-Encoding",
  19.                 "Grpc-Status-Details-Bin");
  20. }));
  21.  
  22. var app = builder.Build();
  23.  
  24. // gRPC-Webミドルウェアを有効にします。
  25. app.UseGrpcWeb();
  26.  
  27. // CORSミドルウェアを有効にします。
  28. app.UseCors();
  29.  
  30. // Configure the HTTP request pipeline.
  31. app.MapGrpcService<AES128EncSrvService>().EnableGrpcWeb().RequireCors("AllowAll");
  32. 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");
  33.  
  34. app.Run();
よくよく見ると、HTTP/1.1を強制する部分でポートを固定化してるんだね、、、

rijndael-srv.csprojを編集(protoファイルの部分)
rijndael-srv.csproj
  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2.   <PropertyGroup>
  3.     <TargetFramework>net8.0</TargetFramework>
  4.     <Nullable>enable</Nullable>
  5.     <ImplicitUsings>enable</ImplicitUsings>
  6.   </PropertyGroup>
  7.   <ItemGroup>
  8.     <Protobuf Include="Protos\rijndael.proto" GrpcServices="Server" />
  9.   </ItemGroup>
  10.   <ItemGroup>
  11.     <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
  12.     <PackageReference Include="Grpc.AspNetCore.Web" Version="2.71.0" />
  13.   </ItemGroup>
  14. </Project>

dotnet buildからのdotnet run
dotnet build
dotnet run
、、、うまくいった
サーバーは実行時にDLLを参照するので、サーバーのプロジェクトルートフォルダにDLL(今回はLinuxなのでlibrijndael.so)を置いておく。

2. クライアント
クライアントはJavaScript今さら入門シリーズの集大成!

プロジェクトフォルダーを作成
mkdir rijndael-jscli
cd rijndael-jscli

もうすでにインストールされているけど、protocとgRPC-Webプラグインをインストールする。
tool-install.sh
  1. #!/bin/sh
  2. # プロトコルバッファコンパイラ(protoc)のインストール
  3. PROTOBUF_VERSION="32.1"
  4. wget "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
  5. unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc_install
  6. sudo mv protoc_install/bin/protoc /usr/local/bin/
  7. sudo mv protoc_install/include /usr/local/include/
  8. rm -rf protoc_install*
  9.  
  10. # gRPC-Webプラグインのインストール
  11. GRPC_WEB_PLUGIN_VERSION="2.0.1"
  12. wget "https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_PLUGIN_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64"
  13. sudo mv "protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64" /usr/local/bin/protoc-gen-grpc-web
  14. 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

npmパッケージのインストール
npm init -y
npm install grpc-web google-protobuf


protoファイルの準備
mkdir Protos
cp ../rijndael-srv/Protos/* Protos

Protos/rijndael.proto
  1. syntax = "proto3";
  2. //option csharp_namespace = "rijndael_srv";
  3. package rijndael_srv;
  4. service AES128EncSrv {
  5.   rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  6. }
  7. message AES128EncReply{
  8.   bytes encoded=1;
  9. }
  10.  
  11. message AES128EncRequest{
  12.   bytes plain=1;
  13.   bytes key=2;
  14. }

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
ができる

ブラウザで開くhtmlファイルの作成(これがUIになる)
index.html
  1. <!DOCTYPE html>
  2. <html lang="ja">
  3. <head>
  4.   <meta charset="UTF-8">
  5.   <title>gRPC-Web Client</title>
  6. </head>
  7. <body>
  8.   <h1>gRPC-Web Client Example</h1>
  9.   <p>AES128 gRPCサーバーを使って暗号文を得る</p>
  10.   <input type="text" size=50 id="plain" placeholder="平文"><br>
  11.   <input type="text" size=50 id="key" placeholder="鍵"><br>
  12.   <button type="button" id="exec">実行</button><br>
  13.   <div id="out"></div><br>
  14.  
  15.   <script src="dist/bundle.js"></script>
  16. </body>
  17. </html>

JavaScriptクライアントの実装(ここが今回のキモ部分)
src/client.js
  1. // 生成されたコード
  2. const {AES128EncRequest} = require('./gen/rijndael_pb.js');
  3. const {AES128EncSrvClient} = require('./gen/rijndael_grpc_web_pb.js');
  4.  
  5. // gRPC-Webサービスのエンドポイントを指定
  6. const client = new AES128EncSrvClient('http://localhost:5052', null, null);
  7.  
  8. let plain=document.getElementById('plain');
  9. let key=document.getElementById('key');
  10. let out=document.getElementById('out');
  11. let button=document.getElementById('exec');
  12.  
  13. function button_clicked(){
  14.   const plain_txt=plain.value;
  15.   const key_txt=key.value;
  16.   const plain_pairs=plain_txt.match(/.{2}/g) || [];
  17.   const key_pairs=key_txt.match(/.{2}/g) || [];
  18.   const plain_arr=new Uint8Array(plain_pairs.map(byteString => parseInt(byteString, 16)));
  19.   const key_arr=new Uint8Array(key_pairs.map(byteString => parseInt(byteString, 16)));
  20.   
  21.   // リクエストを作成
  22.   const request = new AES128EncRequest();
  23.   request.setPlain(plain_arr);
  24.   request.setKey(key_arr);
  25.   
  26.   // AES128Encメソッドを呼び出し(関数名の先頭が勝手に小文字になる)
  27.   client.aES128Enc(request, {}, (err, response) => {
  28.     if (err) {
  29.       //console.error('Error: ', err.message);
  30.       console.error('gRPC error:', err.message, 'Details:', err.details, 'Code:', err.code);
  31.       return;
  32.     }
  33.     const b_encrypted = response.getEncoded();
  34.     // Uint8Arrayを16進数文字列に変換する
  35.     const txt = Array.from(b_encrypted)
  36.       .map(byte => byte.toString(16).padStart(2, '0').toUpperCase())
  37.       .join('');
  38.     console.log(txt);
  39.     out.textContent=txt;
  40.   });
  41. }
  42. button.addEventListener("click",button_clicked);

Webpackのインストールと設定
npm install --save-dev webpack webpack-cli
webpack.config.js作成
webpack.config.js
  1. // webpack.config.js
  2. const path = require('path');
  3. module.exports = {
  4.   entry: './src/client.js',
  5.   output: {
  6.     path: path.resolve(__dirname, 'dist'),
  7.     filename: 'bundle.js',
  8.   },
  9.   mode: 'development',
  10.   resolve: {
  11.     alias: {
  12.       './gen': path.resolve(__dirname, 'src', 'gen')
  13.     }
  14.   }
  15. };

バンドルを実行
npx webpack

3. ブラウザで実行
ブラウザで開く
そして、実行٩( 'ω' )و
できたーやってやったぜー

JavaScript入門は一旦おわろうかな、、、ほんと今さらだけど、まぁまぁおもしろいね(・∀・)

2025年9月19日金曜日

JavaScript今さら入門(5)

 


JavaScriptでAES128暗号文を作ってみる

ちょっとgRPCから離れて、JavaScriptで少し複雑なことをやってみる。例題はいつものやつ(rijndael)。
HTMLに埋め込んで自前でやるからサーバー側にプログラムとか置いて許可を得る必要がない。 とはいえ、JavaScriptの知識は圧倒的に不足しているので、Geminiを使ってC言語のソースコードをJavaScriptに変換してもらった。 、、、これって、もう言語を選ぶ意味がなくなるってこと?まぁ、外部ライブラリに依存していないものならそうなのかも。そりゃそうだと言えばそりゃそうだなんだけど、 すごい時代というか、いい時代というか、恐ろしい時代というか、、、

JavaScriptでrijndael





実行結果例

ソースコードはこちら
  1. <!--ここから-->
  2. <div>JavaScriptでrijndael</div>
  3. <input id="plain" placeholder="平文" size="50" type="text" /><br />
  4. <input id="key" placeholder="鍵" size="50" type="text" /><br />
  5. <button id="exec" type="button">実行</button><br />
  6. <div id="out"></div><br />
  7. <script>
  8.   
  9. /* rijndael.js */
  10. // S-Box
  11. const S = [
  12.     [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76],
  13.     [0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0],
  14.     [0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15],
  15.     [0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75],
  16.     [0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84],
  17.     [0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf],
  18.     [0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8],
  19.     [0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2],
  20.     [0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73],
  21.     [0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb],
  22.     [0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79],
  23.     [0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08],
  24.     [0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a],
  25.     [0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e],
  26.     [0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf],
  27.     [0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
  28. ];
  29. // Inverse S-Box
  30. const invS = [
  31.     [0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb],
  32.     [0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb],
  33.     [0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e],
  34.     [0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25],
  35.     [0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92],
  36.     [0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84],
  37.     [0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06],
  38.     [0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b],
  39.     [0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73],
  40.     [0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e],
  41.     [0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b],
  42.     [0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4],
  43.     [0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f],
  44.     [0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef],
  45.     [0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61],
  46.     [0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d]
  47. ];
  48. // Key Expansion用の変数と定数
  49. const w = Array(11).fill(null).map(() => new Uint8Array(16));
  50. const Rcon = new Uint8Array([0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36]);
  51. /**
  52.  * 4バイトの単語をS-Boxを使って置換します。
  53.  * @param {Uint8Array} inWord - 置換する4バイトの単語
  54.  */
  55. function SubWord(inWord) {
  56.     for (let i = 0; i < 4; i++) {
  57.         inWord[i] = S[(inWord[i] >> 4) & 0x0F][inWord[i] & 0x0F];
  58.     }
  59. }
  60. /**
  61.  * 4バイトの単語を左に1バイトシフトします。
  62.  * @param {Uint8Array} inWord - シフトする4バイトの単語
  63.  */
  64. function RotWord(inWord) {
  65.     const tmp = inWord[0];
  66.     inWord[0] = inWord[1];
  67.     inWord[1] = inWord[2];
  68.     inWord[2] = inWord[3];
  69.     inWord[3] = tmp;
  70. }
  71. /**
  72.  * 1ラウンドの鍵を生成します。
  73.  * @param {number} num - ラウンド番号
  74.  */
  75. function KeyExpansion_Single(num) {
  76.     const tempWord = new Uint8Array(4);
  77.     for (let i = 0; i < 4; i++) {
  78.         tempWord[i] = w[num - 1][12 + i];
  79.     }
  80.     RotWord(tempWord);
  81.     SubWord(tempWord);
  82.     tempWord[0] ^= Rcon[num - 1];
  83.     for (let i = 0; i < 4; i++) {
  84.         w[num][i] = tempWord[i] ^ w[num - 1][i];
  85.         w[num][i + 4] = w[num][i] ^ w[num - 1][i + 4];
  86.         w[num][i + 8] = w[num][i + 4] ^ w[num - 1][i + 8];
  87.         w[num][i + 12] = w[num][i + 8] ^ w[num - 1][i + 12];
  88.     }
  89. }
  90. /**
  91.  * 鍵展開を行います。
  92.  * @param {Uint8Array} key - 16バイトの鍵
  93.  */
  94. function KeyExpansion(key) {
  95.     for (let i = 0; i < 16; i++) {
  96.         w[0][i] = key[i];
  97.     }
  98.     for (let i = 1; i < 11; i++) {
  99.         KeyExpansion_Single(i);
  100.     }
  101. }
  102. /**
  103.  * ラウンド鍵を加算します (XOR演算)。
  104.  * @param {Uint8Array} state - 状態配列
  105.  * @param {number} nRound - ラウンド番号
  106.  */
  107. function AddRoundKey(state, nRound) {
  108.     for (let i = 0; i < 16; i++) {
  109.         state[i] ^= w[nRound][i];
  110.     }
  111. }
  112. /**
  113.  * 状態配列の各バイトをS-Boxで置換します。
  114.  * @param {Uint8Array} state - 状態配列
  115.  */
  116. function SubBytes(state) {
  117.     for (let i = 0; i < 16; i++) {
  118.         state[i] = S[state[i] >> 4][state[i] & 0x0F];
  119.     }
  120. }
  121. /**
  122.  * 状態配列の各バイトをInverse S-Boxで置換します。
  123.  * @param {Uint8Array} state - 状態配列
  124.  */
  125. function invSubBytes(state) {
  126.     for (let i = 0; i < 16; i++) {
  127.         state[i] = invS[state[i] >> 4][state[i] & 0x0F];
  128.     }
  129. }
  130. /**
  131.  * 状態配列の行をシフトします。
  132.  * @param {Uint8Array} state - 状態配列
  133.  */
  134. function ShiftRows(state) {
  135.     const temp = new Uint8Array(8);
  136.     for (let i = 0; i < 4; i++) {
  137.         for (let j = 0; j < 4; j++) {
  138.             temp[j] = state[j * 4 + i];
  139.             temp[j + 4] = state[j * 4 + i];
  140.         }
  141.         for (let j = 0; j < 4; j++) {
  142.             state[j * 4 + i] = temp[j + i];
  143.         }
  144.     }
  145. }
  146. /**
  147.  * 状態配列の行を逆シフトします。
  148.  * @param {Uint8Array} state - 状態配列
  149.  */
  150. function invShiftRows(state) {
  151.     const temp = new Uint8Array(8);
  152.     for (let i = 0; i < 4; i++) {
  153.         for (let j = 0; j < 4; j++) {
  154.             temp[j] = state[j * 4 + i];
  155.             temp[j + 4] = state[j * 4 + i];
  156.         }
  157.         for (let j = 0; j < 4; j++) {
  158.             state[j * 4 + i] = temp[j + 4 - i];
  159.         }
  160.     }
  161. }
  162. /**
  163.  * Galois Field GF(2^8)における乗算を行います。
  164.  * @param {number} a - 被乗数
  165.  * @param {number} b - 乗数
  166.  * @returns {number} 乗算結果
  167.  */
  168. function mul(a, b) {
  169.     let x = 0;
  170.     for (let i = 0x08; i > 0; i >>= 1) {
  171.         if (x & 0x80) {
  172.             x <<= 1;
  173.             x ^= 0x1b;
  174.         } else {
  175.             x <<= 1;
  176.         }
  177.         if (b & i) {
  178.             x ^= a;
  179.         }
  180.     }
  181.     return x & 0xFF; // 8ビットに収まるようにマスク
  182. }
  183. /**
  184.  * 1つの列に対してMixColumns変換を適用します。
  185.  * @param {Uint8Array} r - 4バイトの列
  186.  */
  187. function MixColumn_single(r) {
  188.     const t = r.slice(0, 4); // 配列のコピー
  189.     r[0] = mul(t[0], 2) ^ mul(t[1], 3) ^ mul(t[2], 1) ^ mul(t[3], 1);
  190.     r[1] = mul(t[1], 2) ^ mul(t[2], 3) ^ mul(t[3], 1) ^ mul(t[0], 1);
  191.     r[2] = mul(t[2], 2) ^ mul(t[3], 3) ^ mul(t[0], 1) ^ mul(t[1], 1);
  192.     r[3] = mul(t[3], 2) ^ mul(t[0], 3) ^ mul(t[1], 1) ^ mul(t[2], 1);
  193. }
  194. /**
  195.  * 状態配列の列に対してMixColumns変換を適用します。
  196.  * @param {Uint8Array} state - 状態配列
  197.  */
  198. function MixColumns(state) {
  199.     for (let i = 0; i < 4; i++) {
  200.         MixColumn_single(state.subarray(i * 4, i * 4 + 4)); // subarrayでビューを作成
  201.     }
  202. }
  203. /**
  204.  * 1つの列に対してInverse MixColumns変換を適用します。
  205.  * @param {Uint8Array} r - 4バイトの列
  206.  */
  207. function invMixColumn_single(r) {
  208.     const t = r.slice(0, 4); // 配列のコピー
  209.     r[0] = mul(t[0], 14) ^ mul(t[1], 11) ^ mul(t[2], 13) ^ mul(t[3], 9);
  210.     r[1] = mul(t[1], 14) ^ mul(t[2], 11) ^ mul(t[3], 13) ^ mul(t[0], 9);
  211.     r[2] = mul(t[2], 14) ^ mul(t[3], 11) ^ mul(t[0], 13) ^ mul(t[1], 9);
  212.     r[3] = mul(t[3], 14) ^ mul(t[0], 11) ^ mul(t[1], 13) ^ mul(t[2], 9);
  213. }
  214. /**
  215.  * 状態配列の列に対してInverse MixColumns変換を適用します。
  216.  * @param {Uint8Array} state - 状態配列
  217.  */
  218. function invMixColumns(state) {
  219.     for (let i = 0; i < 4; i++) {
  220.         invMixColumn_single(state.subarray(i * 4, i * 4 + 4));
  221.     }
  222. }
  223. /**
  224.  * AES暗号化処理を行います。
  225.  * @param {Uint8Array} data - 暗号化する16バイトのデータ
  226.  * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
  227.  */
  228. function Cipher(data, key) {
  229.     AddRoundKey(data, 0);
  230.     for (let i = 1; i < 10; i++) {
  231.         SubBytes(data);
  232.         ShiftRows(data);
  233.         MixColumns(data);
  234.         AddRoundKey(data, i);
  235.     }
  236.     SubBytes(data);
  237.     ShiftRows(data);
  238.     AddRoundKey(data, 10);
  239. }
  240. /**
  241.  * AES復号化処理を行います。
  242.  * @param {Uint8Array} data - 復号化する16バイトのデータ
  243.  * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
  244.  */
  245. function invCipher(data, key) {
  246.     AddRoundKey(data, 10);
  247.     for (let i = 9; i > 0; i--) {
  248.         invShiftRows(data);
  249.         invSubBytes(data);
  250.         AddRoundKey(data, i);
  251.         invMixColumns(data);
  252.     }
  253.     invShiftRows(data);
  254.     invSubBytes(data);
  255.     AddRoundKey(data, 0);
  256. }
  257. // ユーザーが利用するためのメイン関数
  258. // C言語のAES128Encrypt/Decrypt関数を模倣
  259. const AES128 = {
  260.     /**
  261.      * AES128暗号化を実行します。
  262.      * @param {Uint8Array} plain - 16バイトの平文
  263.      * @param {Uint8Array} key - 16バイトの鍵
  264.      * @returns {Uint8Array} 暗号化された16バイトのデータ
  265.      */
  266.     encrypt(plain, key) {
  267.         const enc = new Uint8Array(16);
  268.         enc.set(plain);
  269.         KeyExpansion(key);
  270.         Cipher(enc, key);
  271.         return enc;
  272.     },
  273.     /**
  274.      * AES128復号化を実行します。
  275.      * @param {Uint8Array} enc - 16バイトの暗号文
  276.      * @param {Uint8Array} key - 16バイトの鍵
  277.      * @returns {Uint8Array} 復号化された16バイトのデータ
  278.      */
  279.     decrypt(enc, key) {
  280.         const plain = new Uint8Array(16);
  281.         plain.set(enc);
  282.         KeyExpansion(key);
  283.         invCipher(plain, key);
  284.         return plain;
  285.     }
  286. };
  287. // C言語のmain関数を模倣したテストコード
  288. function main() {
  289.     const key = new Uint8Array([0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f]);
  290.     const data = new Uint8Array([0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff]);
  291.     // 暗号化
  292.     const encryptedData = AES128.encrypt(data, key);
  293.     console.log("Encrypted Data:", Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
  294.     // 復号化
  295.     const decryptedData = AES128.decrypt(encryptedData, key);
  296.     console.log("Decrypted Data:", Array.from(decryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
  297. }
  298. // main関数を実行
  299. // main();
  300.   
  301. let plain=document.getElementById('plain');
  302. let key=document.getElementById('key');
  303. let out=document.getElementById('out');
  304. let button=document.getElementById('exec');
  305. function button_clicked(){
  306.   const plain_txt=plain.value;
  307.   const key_txt=key.value;
  308.   const plain_pairs=plain_txt.match(/.{2}/g) || [];
  309.   const key_pairs=key_txt.match(/.{2}/g) || [];
  310.   const plain_arr=new Uint8Array(plain_pairs.map(byteString => parseInt(byteString, 16)));
  311.   const key_arr=new Uint8Array(key_pairs.map(byteString => parseInt(byteString, 16)));
  312.   const encryptedData = AES128.encrypt(plain_arr, key_arr);
  313.   out.textContent=Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join('');
  314. }
  315. button.addEventListener("click",button_clicked);
  316. </script>
  317. <!--ここまで-->

AIもこういうのは一発でうまくいくことが多いな、、、

2025年9月18日木曜日

JavaScript今さら入門(4)

 

今さらなんだけど、JavaScriptに挑戦してみる。JavaScriptでgRPC Clientを作ってみる試み。どうせならHTMLから実行したいし、結果をブラウザに表示したい。



HTMLに埋め込んだJavaScriptでgRPCしたんだから結果はConsoleじゃなくてブラウザに表示したくなるよね。
ってことで、JavaScript今さら入門(1)を見ながら書き直していく。

index.html
  1. <!DOCTYPE html>
  2. <html lang="ja">
  3. <head>
  4.   <meta charset="UTF-8">
  5.   <title>gRPC-Web Client</title>
  6. </head>
  7. <body>
  8.   <h1>gRPC-Web Client Example</h1>
  9.   <p>Greeter</p>
  10.   <input type="text" id="user" placeholder="user"><br>
  11.   <button type="button" id="greet">greet</button><br>
  12.   <div id="out"></div><br>
  13.   <script src="dist/bundle.js"></script>
  14. </body>
  15. </html>

src/client.js
  1. let out=document.getElementById('out');
  2. let user=document.getElementById('user');
  3. let button=document.getElementById('greet');
  4.  
  5. // 生成されたコード
  6. const {HelloRequest} = require('./gen/greet_pb.js');
  7. const {GreeterClient} = require('./gen/greet_grpc_web_pb.js');
  8.  
  9. // gRPC-Webサービスのエンドポイントを指定
  10. const client = new GreeterClient('http://localhost:5052', null, null);
  11.  
  12. function button_clicked(){
  13.   let user_value=user.value;
  14.  
  15.   // リクエストを作成
  16.   const request = new HelloRequest();
  17.   request.setName(user_value);
  18.  
  19.   // SayHelloメソッドを呼び出し
  20.   client.sayHello(request, {}, (err, response) => {
  21.     if (err) {
  22.       console.error('Error: ', err.message);
  23.       return;
  24.     }
  25.     let response_message=response.getMessage();
  26.     console.log('Greeting:', response_message);
  27.     out.textContent=response_message;
  28.   });
  29. }
  30.  
  31. button.addEventListener("click",button_clicked);

でけた
埋め込み用のコードを生成
npx webpack

ブラウザで読み込んでみる

で、なんか入力してボタンを押してみる

うまくいったー

2025年9月17日水曜日

JavaScript今さら入門(3)

 

今さらなんだけど、JavaScriptに挑戦してみる。JavaScriptでgRPC Clientを作ってみる試み。どうせならHTMLから実行したい。

唐突にJavaScriptでgRPCしたんだけど、どうも生のgRPC(HTTP/2)のJavaScriptはHTMLに埋め込めないらしくて、gPRC-Webにせんといかんらしい。サーバー側にも変更が必要らしいので、別途検討してみた(こちら)。
なんだけど、じつはそれだけじゃ足りなかったので、加えてこちら
では、ようやく、HTMLに埋め込んだJavaScriptからgRPCしてみるでー
今回もGeminiに聞いてみるけど、参考サイトはこちら

プロジェクトフォルダを作成して移動
mkdir grpcweb-jscli
cd grpcweb-jscli

protocとprotoc-gen-grpc-webのインストール
tool-install.sh
  1. #!/bin/sh
  2. # プロトコルバッファコンパイラ(protoc)のインストール
  3. PROTOBUF_VERSION="32.1"
  4. wget "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip"
  5. unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc_install
  6. sudo mv protoc_install/bin/protoc /usr/local/bin/
  7. sudo mv protoc_install/include /usr/local/include/
  8. rm -rf protoc_install*
  9.  
  10. # gRPC-Webプラグインのインストール
  11. GRPC_WEB_PLUGIN_VERSION="2.0.1"
  12. wget "https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_PLUGIN_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64"
  13. sudo mv "protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64" /usr/local/bin/protoc-gen-grpc-web
  14. sudo chmod +x /usr/local/bin/protoc-gen-grpc-web
chmod 755 tool-install.sh
./tool-install.sh

protoc-gen-jsのインストール
sudo npm install -g protoc-gen-js

npmパッケージのインストール
npm init -y
npm install grpc-web google-protobuf

protoファイルの準備
mkdir Protos
cp ../grpcweb-srv/Protos/* Protos

Protos/greet.proto
  1. syntax = "proto3";
  2.  
  3. //option csharp_namespace = "grpcweb_srv";
  4.  
  5. package greet;
  6.  
  7. // The greeting service definition.
  8. service Greeter {
  9.   // Sends a greeting
  10.   rpc SayHello (HelloRequest) returns (HelloReply);
  11. }
  12.  
  13. // The request message containing the user's name.
  14. message HelloRequest {
  15.   string name = 1;
  16. }
  17.  
  18. // The response message containing the greetings.
  19. message HelloReply {
  20.   string message = 1;
  21. }
C#で作ったprotoファイルと同じなんだけどoption csharp_namespace=...ってのはいらんでしょっでコメントアウト

JavaScriptコードの生成
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/greet.proto

JavaScriptクライアントの実装
src/client.js
  1. // 生成されたコード
  2. const {HelloRequest} = require('./gen/greet_pb.js');
  3. const {GreeterClient} = require('./gen/greet_grpc_web_pb.js');
  4.  
  5. // gRPC-Webサービスのエンドポイントを指定
  6. const client = new GreeterClient('http://localhost:5052', null, null);
  7.  
  8. // リクエストを作成
  9. const request = new HelloRequest();
  10. request.setName('JavaScript Client');
  11.  
  12. // SayHelloメソッドを呼び出し
  13. client.sayHello(request, {}, (err, response) => {
  14.   if (err) {
  15.     console.error('Error: ', err.message);
  16.     return;
  17.   }
  18.   console.log('Greeting:', response.getMessage());
  19. });

HTMLファイルへの埋め込み
Webpackのインストールと設定
npm install --save-dev webpack webpack-cli
webpack.config.js作成
webpack.config.js
  1. // webpack.config.js
  2. const path = require('path');
  3. module.exports = {
  4.   entry: './src/client.js',
  5.   output: {
  6.     path: path.resolve(__dirname, 'dist'),
  7.     filename: 'bundle.js',
  8.   },
  9.   mode: 'development',
  10.   resolve: {
  11.     alias: {
  12.       './gen': path.resolve(__dirname, 'src', 'gen')
  13.     }
  14.   }
  15. };
バンドルを実行
npx webpack

HTMLファイルへの埋め込み
index.html
  1. <!DOCTYPE html>
  2. <html lang="ja">
  3. <head>
  4.   <meta charset="UTF-8">
  5.   <title>gRPC-Web Client</title>
  6. </head>
  7. <body>
  8.   <h1>gRPC-Web Client Example</h1>
  9.   <p>コンソールを確認してください。</p>
  10.  
  11.   <script src="dist/bundle.js"></script>
  12. </body>
  13. </html>

んで、index.htmlをブラウザで開いて、chromeならCTRL+SHIFT+Iでデベロッパーツールを開いて、Consoleを見ると(てか、最近のブラウザってローカルのファイルを開く方法ってドラッグアンドドロップしかないの?)
やったぜ
これ使いこなせたらやりたい放題じゃん(何をじゃ?)ꉂꉂ(˃▿˂๑)