psqlの暗号化と復号

PostgreSQLの機能にpgcryptという暗号関数を提供するライブラリがある

CREATE TEMPORARY FUNCTION decrypt(_text STRING) RETURNS STRING LANGUAGE js AS
"""
	if (_text === null) {return _text}
	let base64_text = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(_text))
	let key = CryptJS.enc.Utf.parse('PASSWORD\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000')
	let iv = CryptoJS.enc.Utf8.parse('')
	let options = { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 };
	let _decrypt = CryptoJS.AES.decrypt(base_text, key, options);
	return _decrypt.toString(CryptoJS.enc.Utf8);
""" OPTIONS (library="gs://xxxxxxxxx/crypto-js-4.1.1/crypto-js.js");

SELECT decrypt(substring('\\xba9a0a4189f691d761c431c89f42e319', 3));

データベースの暗号化

データベース暗号化の種類
1. データベース管理システム(DBMS)による暗号化
DBMSが持つ暗号化機能を使用する方法
しかし、サーバやパソコンのメモリ上には暗号化されていないデータが一時的に残る場合があり、高度な技術があれば読み取られてしまう恐れがある

2.ストレージ(記録装置)の暗号化
データを記録するストレージ(記録装置)自体を暗号化する方法。OSやファイルシステム、ストレージが備える暗号化機能を使ってデータが書き込まれる際に自動的に暗号化し、読み取るときに同じく自動的に解読される仕組み
OSから見ると暗号化されない状態

3.データ自体の暗号化
データ自体を暗号化してからデータベースに記録する

アクセス制御を含む暗号化を考える必要がある
情報機器の紛失・盗難対策
誤操作対策
情報機器の処分時の対策

アクセス制御など複数の対策を組み合わせて実施する訳ね

暗号処理のパディングモード(パディング方式)

分割する時にブロックで綺麗に割り切れない場合は、パディングで穴埋めします。

### パディングモード
・PKCS#5 PKCS#7 : AESと組み合わせることが多い
・Optimal Asymmetric Encryption Padding(OAEP) : RSAと組み合わせることが多い。その場合はRSA-OAEPと呼ばれる

def self.encrypt(data, pass, salt)
	cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
	key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, 1000, cipher.key_len+cipher.iv_len)
	key = key_iv[0, cipher.key_len]
	iv = key_iv[cipher.key_len, cipher.iv_len]
	cipher.encrypt
	cipher.key = key
	cipher.iv = iv
	encryptvalue = cipher.update(data) + cipher.final
	return Base64.b64encode(encryptvalue)
end	

test_214_bytes.txt 214 bytes
test_215_bytes.txt 215 bytes
openssl rsautl -encrypt -oaep -pubin -inkey public-key.pem -in test_214_bytes.txt -out test_214_bytes.enc
openssl rsautl -encrypt -oaep -pubin -inkey public-key.pem -in test_215_bytes.txt -out test_215_bytes.enc

暗号アルゴリズム AES, DES, RC4, RSA, 楕円曲線暗号

共通鍵暗号方式には「DES」「RC4」「AES」などがある

### AES
AESは無線LANなどに用いられる暗号化アルゴリズムの一つで Advanced Encryption Standardの略で標準的に使われている。共通鍵暗号で、データの送信者と受信者が同じ暗号鍵を用いて暗号化と復号を実行する DES以上の強度を持つ。処理速度も優れている
AESは128,192, 256bitから鍵長を選んで利用可能
SubBytes, ShiftRows, MixColumns, AddRoundKeyなどの変換を行う
WPA2(無線LAN), SSL/TLS化通信(https://)、ファイルの暗号化などで使われている
WifiのWPA2, WPA3はAESを採用している

### DES/3DES
1977年にアメリカ連邦政府標準が採用した暗号アルゴリズム
DESは鍵長が短い(56bit)などの難点があった 総当たり攻撃に弱い
鍵長は56bitですが、パリティチェック用の8bitを加えて64bitとして扱う
この弱点を補うために2DES(DESを2回繰り返す112bitの鍵長)、3DES(DESを3回繰り返す、168bitだが処理に時間がかかる)が登場したが別の攻撃に弱いなどの弱点が見つかった
DESでは64bitのデータを半分(LとR)に分割し、同じような処理を繰り返す過程がある。この繰り返しの単位をラウンドと言う

### RC4(Rivest’s Cipher4)
ブロックのが長さ(鍵長)を自由に設定できるアルゴリズム
鍵の長さを40〜2048bitの間で自由に設定できる
逐次暗号化するすストリーム暗号 簡単にセキュリティを突破されてしまう
SSL3.0以前やWifiのWEPやWAPはRC4を用いている

### RSA
公開鍵暗号方式、暗号化と復号に別の鍵を使用する
「大きな数字を素因数分解するには膨大な時間がかかる」ことを利用したアルゴリズム。
安全性が高いが処理に時間がかかる

### 楕円曲線暗号
楕円曲線( y2 = x3 + ax + b で表される曲線)の数学的な特性を安全性の根拠とする暗号。「一方向の計算は容易でも、反対方向の計算は困難」な性質が使われることで、公開鍵が知られても容易に秘密鍵を割り出せない仕組み。暗号化や復号に必要な計算量が少なく、他のアルゴリズムより短い鍵で同等の強度が得られることから、活用の幅が広がっている。

### ハイブリッド暗号方式
共通鍵暗号方式と公開鍵暗号方式の特徴を組み合わせた方式
1. 受信者が公開鍵と秘密鍵を作成する
2. 送信者は通信で使用する「共通の秘密鍵」を作成する
3. 送信者は取得した受信者の公開鍵を使って「共通の秘密鍵」を暗号化し、送付する
4. 受信者は秘密鍵を使って「共通の秘密鍵」を復号する
ハイブリッド暗号方式は、共通鍵暗号方式の持つ鍵の受け渡しの課題を公開鍵暗号方式でカバーし、公開鍵暗号方式が持つ処理速度の遅さを共通鍵暗号方式でカバー

### ハッシュ化
不可逆性(元に戻せない性質)を持つ
ユーザーがパスワードを初期登録する際にハッシュ化によってパスワードが変換されハッシュ値が得られます。ログインをする際は、入力されたパスワードのハッシュ値と登録の際に保存されたハッシュ値を比較することで、ユーザーが同一であると認証できる

OpenSSLの実装

// 暗号化
openssl_encrypt($data, $method, $key, $options, $iv);

// 復号
openssl_decrypt($data, $method, $key, $options, $iv);

暗号化時は bin2hex、復号時は hex2bin を用いる

$enc_string = bin2hex(openssl_encrypt($data, $method, $key, 0, $iv));
$dec_string = openssl_decrypt(hex2bin($data), $method, $key, 0, $iv);
$key = "passphrase";
$str = "abcdef";

$encrypt = openssl_encrypt($str, 'aes-256-ecb', $key);
echo $encrypt;

echo "\n";
$decrypt = openssl_decrypt($encrypt, 'aes-256-ecb', $key);
echo $decrypt;

$ php index.php
HR6LV+jGY5iU8Rycs+Jv8g==
abcde

平文の暗号化 – 暗号処理の流れ

### 暗号化の流れ
(1)ブロック長に満たない部分をパディング方式で補完する
(2)アルゴリズムに対応したブロック長に分割する
※この時の分割ルールを決めるのが暗号利用モード
※分割モードによっては初期値が必要で、それがIV(初期ベクトル)
(3)暗号鍵を用いてアルゴリズムで暗号化
(4)分割されたデータを結合

複合化の流れ
(1)アルゴリズムに対応したブロック長に分割する
(2)暗号鍵を用いてアルゴリズムで複合化する
(3)暗号利用モードを元に分割されたデータを結合する
※分割モードによっては初期値が必要で、これがIV(初期化ベクトル)
(4)補完されたパディングをパディング方式で除去する

ブロック暗号では、ブロック長、秘密鍵、暗号アルゴリズム、暗号利用モード、パディング方式、IV(初期化ベクトル)のキーワードが重要
使用できる暗号化アルゴリズムは組み込まれているライブラリに依存
暗号利用モードにはECB, CBD, OFB, CFB
パディング方式はZeroBytePadding, パディング不要な状態であればZeroBytePaddingは何もしないのでPKCS#5 Paddingなどは独自に実装することができる。NoPaddingはできない

$ sudo apt install php-dev libmcrypt-dev php-pear
$ sudo pecl channel-update pecl.php.net
$ sudo pecl install mcrypt-1.0.1

function cipher_encrypt($input, $key){
  // 指定した暗号のブロックサイズを得る
  $size = mcrypt_get_block_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
  // PKCS5Padブロック長に満たないサイズを埋める
  $input = pkcs5_pad($input, $size);
  // 使用するアルゴリズムおよびモードのモジュールをオープンする
  $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_ECB, '');
  // オープンされたアルゴリズムのIVの大きさを返す
  $ivsize = mcrypt_enc_get_iv_size($td);
  // MCRYPT_RANDの初期化を行う
  srand();

  // 乱数ソースから初期化ベクトル(IV)を生成する
  // ECB以外では複合にIVが必要
  // ECBではIVは使用されないがIVがないとエラーが出る
  $iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
  // 暗号化に必要な全てのバッファを初期化
  mycrypt_generic_init($td, $key, $iv);
  // データを暗号化
  $data = mcrypt_generic($td, $input);
  // 暗号化モジュールを修了する
  mycrypt_generic_deinit($td);
  // mcryptモジュールを閉じる
  mcrypt_module_close($td);
  return $data;
}

// 複合化を行う
function cipher_decrypt($input, $key){
  // 指定した暗号のブロックサイズを得る
  $size = mcrypt_get_block_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
  // 使用するアルゴリズムおよびモジュールをオープンする
  $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_ECB, '');
  // オープンされたアルゴリズムのIVの大きさを返す
  $ivsize = mcrypt_enc_get_iv_size($td);
  // MCRYPT_RANDの初期化を行う
  srand();

  // 乱数ソースから初期化ベクトル(IV)を生成する
  // ECB以外では複合にIVが必要
  // ECBではIVは使用されないがIVがないとエラーが出る
  $iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
  // 暗号化に必要な全てのバッファを初期化する
  mcrypt_generic_init($td, $key, $iv);
  // データを複合する
  $data = mdecrypt_generic($td, $input);
  // 暗号化モジュールを修了
  mcrypt_generic_deinit($td);
  // mcryptモジュールを閉じる
  mcrypt_module_close($td);
  // PKCS5Padding埋められたバイト値を除く
  $data = pkcs5_unpad($data, $size);
  return $data;
}

// PKCS5Padding
// ブロック長に満たないサイズを埋める
function pkcs5_pad($text, $blocksize){
  $pad = $blocksize - (strlen($text) % $blocksize);
  return $text . str_repeat(chr($pad), $pad);
}

// PKCS5Padding
// 埋められたバイト値を除く
function pkcs5_unpad($text){
  $pad = ord($text{strlen($text)-1});
  if($pad > strlen($text)) return false;
  if(strspn($text, chr($pad), strlen($text) - $pad)!= $pad) return false;
  return substr($text, 0, -1 * $pad);
}

// 暗号化例
$data = "この文字を暗号化する";
$key = "ABCDEF";

echo "元データ:" . $data;
echo "<br>";
$encrypt = cipher_encrypt($data, $key);
echo "暗号化されたデータ:". $encrypt;
echo "<br>";
$decrypt = cipher_decrypt($encrypt,$key);
echo "複合化されたデータ:". $decrypt;

pecl/mcrypt requires PHP (version >= 7.2.0, version <= 7.3.0, excluded versions: 7.3.0), installed version is 7.4.3-4ubuntu2.18 No valid packages found install failed 仕組みはわかったけど、mcryptではなく、openssl_ecnrypt出ないと駄目やな

ブロック暗号の主なモード(ECB, CBC, CFB, OFB, CTR)

ECBモード: Electronic CodeBook mode(電子符号表モード)
CBCモード: Cipher Block Chaining mode(暗号ブロック連鎖モード)
CFBモード: Cipher-FeedBack mode(暗号フィードバックモード)
OFBモード: Output-FeedBack mode(出力フィードバックモード)
CTRモード: CounTeR mode (カウンタモード)

### ECBモード
平文ブロックを暗号化したものが、そのまま暗号文ブロックになる
最後の平文ブロックがブロック長に満たない場合にはパディングと呼ばれるデータを埋める
=> たくさんあるモードの中で最もシンプルだが最も機密性の低いモード
平文の中に同じ値を持つ平文ブロックが複数存在した場合、全て同じ値の暗号文ブロックに変換されてしまう。暗号文を見るだけで平文の繰り返しがわかってしまう

### CBCモード
CBCモードは1つ前の暗号文ブロックと平文ブロックのXORをとり、そのXORの値を暗号化を行う。CBCモードも最後の平文ブロックがブロック長に満たない場合にはパディングを行う
SSL/TLSではCBCモードが使われている
最初の平文ブロックを暗号化する際に、「1つ前の暗号文ブロック」は存在しないので、「1つ前の暗号ブロック」の代わりのビット列を1ブロック分用意する必要がある。このビット列をIV(initial vector)

### CFBモード
CFBモードは1つ前の暗号文ブロックを暗号アルゴリズムの入力に戻す
フィードバックとは、入力へ戻すということを意味する
CFBモードで暗号化して得られる値を鍵ストリームという
ECBやCBCモードでは暗号化アルゴリズムによって直接暗号化されるが、CFBは直接暗号化されるわけではない

### OFBモード
暗号アルゴリズムの出力を暗号アルゴリズムの入力へフィードバック
“1つ前の暗号ブロックを暗号アルゴリズムで暗号化した値”と”平文ブロック”のXOR=暗号文ブロックとなる
OFBでもCBC, CFBと同様にIVを使用する
平文ブロックと暗号アルゴリズムの出力とXORして暗号文ブロックを作っている CFBモードに似ている
OFBモードでは、平文ブロックとは無関係に暗号アルゴリズムを前もってぐるぐる回し、XORするためのビット列をしておく

### CTRモード
1ずつ増加していくカウンタを暗号化して鍵ストレーむを作り出すストリーム暗号
“カウンタを暗号化した値”と”平文ブロック”のXOR=暗号文ブロックとなる
カウンタの初期値は暗号化のたびに異なる値(ノンス)を元にして作る

RSA暗号

function rsa_key_generate (
  $p,
  $q
){
  if ($p == $q) {
    return false;
  }

  $n = (string)gmp_mul($p, $q); //mul-> 乗算
  $n_ gmp_mul(gmp_sub($p, '1'), gmp_sub($q, '1')); // 減算(p-1)(q-1)
  $rand = gmp_random_range(0, gmp_sub($n_, '1')); // random_range -> ランダムな数 0 ~ (n')

  while(true) {
    $coprime_numbers = (string)gmp_gcd($rand, $_n); // gcd -> 最大公約数を返す

    // 最大公約数が1なら互いに素な数
    if($coprime_numbers === '1') {
      $e = (string)$rand;
      break;
    }
    // 非互に素な数減算し再計算
    $rand = gmp_sub($rand, '1');
  }

  $d = (string)gmp_invert($e, $n_); // n'を法としたeの逆数

  // 秘密鍵:d 公開鍵:n, e
  return [$d, $n, $e];
}

function rsa_encryption (
  $a, //暗号化対象の平文
  $n, //公開鍵1
  $e //公開鍵2
){
  $b = [];
  foreach ($a as $value) {
    // aの一つを2乗
    $a_e = gmp_pow($value, $e);
    // e乗した値をnで割った余りを格納
    $b[] = (string)gmp_div_r($a_e, $n);
  }
  return $b;
}

function composite (
  $b, // 暗号文
  $d, // 秘密鍵
  $n // 公開鍵
){
    $a = [];
    foreach ($b as $value) {
      // bの一つをd乗する
      $b_d = gmp_pow($value, $d); // 
      // d乗した値をnで割った余りを格納
      $a[] = (string)gmp_div_r($b_d, $n);
    }
    return $a;
  }
  return $b;
}

// 平文を数値配列に変換
function convert_string_to_integer(
  $str
){
  // 初期配列
  $ord_array = [];

  for ($i = 0; $i < mb_strlen($str); $i ++) {
    $value = mb_substr($str, $i, 1);
    $ord_array[] = mb_ord($value);
  }
  return $ord_array;
}

// 数値配列を文字列に変換
function convert_integer_to_string (
  $int_array
) {
  // 初期文字列
  $chr = '';
  // 配列の数値を全て文字列に変換
  foreach ($int_array as $value){
    // 数値を文字列に
    $chr .= mb_char($value);
  }
  return $chr;
}

【ChatGPT】PHPでリクエストを送ってみる

$input_json = file_get_contents('php://input');
$post = json_decode( $input_json, true );
$req_question = $post['prompt'];

$result = array();

// APIキー
$apiKey = '***';

//openAI APIエンドポイント
$endpoint = 'https://api.openai.com/v1/chat/completions';

$headers = array(
  'Content-Type: application/json',
  'Authorization: Bearer ' . $apiKey
);

// リクエストのペイロード
$data = array(
  'model' => 'gpt-3.5-turbo',
  'messages' => [
    [
    "role" => "system",
    "content" => "新宿はどんな所ですか?"
    ],
    // [
    // "role" => "user",
    // "content" => $req_question
    // ]
  ]
);

// cURLリクエストを初期化
$ch = curl_init();

// cURLオプションを設定
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

// APIにリクエストを送信
$response = curl_exec($ch);

// cURLリクエストを閉じる
curl_close($ch);

// 応答を解析
$result = json_decode($response, true);

// 生成されたテキストを取得
$text = $result['choices'][0]['message']['content'];

var_dump($result);

レスポンスは返ってくるが、時間がかかりますね。
$ php index.php
array(6) {
[“id”]=>
string(38) “chatcmpl-7jHpzpu5LnjqM2dbxoBQrx0Nrp0DQ”
[“object”]=>
string(15) “chat.completion”
[“created”]=>
int(1691027683)
[“model”]=>
string(18) “gpt-3.5-turbo-0613”
[“choices”]=>
array(1) {
[0]=>
array(3) {
[“index”]=>
int(0)
[“message”]=>
array(2) {
[“role”]=>
string(9) “assistant”
[“content”]=>
string(1323) “新宿は東京都内で最も繁華なエリアの一つです。駅周辺には高層ビルが立ち並び、多くの人が行き交います。新宿駅は日本でも最も利用者が多い駅の一つで、多くの鉄道路線が交差し、バスターミナルもあるため、アクセスが非常に便利です。

新宿には大型商業施設やデパート、ショッピングモールが集まっており、様々なショップやレストランがあります。また、歌舞伎町という繁華街もあり、夜になると多くの人々で賑わいます。歓楽街として知られており、多くの居酒屋、バー、クラブがあります。

また、新宿は文化施設も充実しており、新宿御苑や東京都庁舎、新宿中央公園などの公共の場所で自然に触れることもできます。さらに、新宿の西側には高層ビルが連なる都市の景色が楽しめる新宿西口地区もあります。

新宿はまた、交通の要所としても知られており、多くの人々が通勤や買い物などで訪れます。そのため、駅周辺は常に混雑していることが多いですが、多くの施設やイベントが盛り上がっているため、観光客や地元の人々にとっても魅力的な場所です。”
}
[“finish_reason”]=>
string(4) “stop”
}
}
[“usage”]=>
array(3) {
[“prompt_tokens”]=>
int(18)
[“completion_tokens”]=>
int(490)
[“total_tokens”]=>
int(508)
}
}

ChatGPTを動かしてみる

$ pip install openapi

import openapi

openai.api_key = '***'

response = openai.ChatCompletion.create(
	mmodel="gtp-3.5-turbo",
	messages=[
		{"role": "system", "content": "You are an assistant that knows a lot about animals."},
		{"role": "user", "content": "Tell me about elephants."},
	]

)
print(response['choices'][0]['message']['content'])

うーむ、なんか上手くいかんな