Pythonとphp間でRSA暗号

# 実行環境
- `MacOS 12.6`
- `Python 3.10.6`
- `PHP8.1`

# モジュールのインストール
Python側でrsaモジュールを使うのでインストールします
```shell
pip install rsa
```

# PEMファイル(鍵)の準備
PEMファイルを生成します
生成したいディレクトリで実行してください
```shell
openssl genrsa 2048 > private_key.pem
openssl rsa -in private_key.pem -pubout -out public_key.pem
```
実行するとこんな感じのファイルができます
```:private_key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA8DoEBfIPUiTOBEivztYvcXVhZONbnbP8rihRFyRkaxBd9QVR
DG66TFefuMpbhLDTjD...p6qeqxzh2kBiTIfQDSQNakN8/mARLaLPlFN5Ly2QlTa
7aXFjs/+P6L17kD9DH25mTbTgpHz0OxjLmK/4ovsgMgTzkQpJa0=
-----END RSA PRIVATE KEY-----
```
```:public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8DoEBfIPUiTOBEivztYv
cXVhZONbnbP8rihR...u37fLCSFgQZm9CbT67R8hGbVshqfH0Jpye68Oy6LMEHHv
e9tcc+MdmMLNiq3DLPzF7IgGPjyB9C3BCJzrHQwO7PiVcg6VAxz7aA+7f01VFHfu
9wIDAQAB
-----END PUBLIC KEY-----
```

# Pythonで暗号化→phpで復号化
値の受け渡し方は色々なやり方があると思うのでここでは省略します
### Python(暗号化)
```python:encryption.py
import base64
import rsa

text = "hello world!" # 暗号化する文字列
pub_key_path = "./public_key.pem" # 公開鍵のパス

text_bytes = bytes(text, encoding="utf-8")
with open(pub_key_path, "rb") as f:
    public_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())

cipher = rsa.encrypt(text_bytes, public_key)
cipher_encode = base64.b64encode(cipher)
cipher_str = cipher_encode.decode("utf-8")

print(cipher_str) # 暗号化された文字列
```
成功すると`LDa0cxKYpkGKiKSGMkm+z...EvNh+nqnNRQ==`のように暗号化された文字が出力されます

### PHP(復号化)
```php:decoding.php
<?php
$cipher = '{Python側で出力された暗号化された文字列}';
$privateKeyPath = './private_key.pem'; // 秘密鍵のパス
$privateKey = file_get_contents($privateKeyPath);

$cipherDecode = base64_decode($cipher);
openssl_private_decrypt($cipherDecode, $decipher, $privateKey);

echo $decipher.PHP_EOL;
?>
```
成功すると暗号化前の文字列が出力されます
失敗すると何も出力されないので気をつけてください

# PHPで暗号化→Pythonで復号化
### PHP(暗号化)
```php:encryption.php
<?php
$text = 'Hello World!'; // 暗号化する文字列
$pubKeyPath = './public_key.pem'; // 公開鍵のパス

$pubKey = file_get_contents($pubKeyPath);

openssl_public_encrypt($text, $cipher, $pubKey);
$cipherEncode = base64_encode($cipher);

echo $cipherEncode.PHP_EOL;
?>
```
成功すると上と同じく`E8Wa8PncC/m1UdK...HSrpAnm9wmY40rAKY+dz2OA==`のように暗号化された文字が出力されます

### Python(復号化)
```python:decoding.py
import base64
import rsa

text = "{PHP側で出力された暗号化された文字列}"
private_key_path = "./private_key.pem" # 秘密鍵のパス

with open(private_key_path, "rb") as f:
    private_key = rsa.PrivateKey._load_pkcs1_pem(f.read())

cipher_decode = base64.b64decode(text)
decipher = rsa.decrypt(cipher_decode, private_key)
decipher_str = decipher.decode("utf-8")

print(decipher_str)
```
成功すると暗号化前の文字列が出力されます

# 注意点
- `privateKey`は絶対に公開しないでください
- クエリパラメータ等を使ってPythonからPHPへ値を受け渡すときは`+`や`=`を空白やパラメータと見られることがあるので受け渡す時は、
```diff_python:encryption.py(11~13行目)
cipher = rsa.encrypt(text_bytes, public_key)
cipher_encode = base64.b64encode(cipher)
- cipher_str = cipher_encode.decode("utf-8")
+ cipher_str = cipher_encode.decode("utf-8").replace('+', '%2B').replace("=", "%3D")
```
のようにreplaceを使ってエンコードしてください

# おまけ: postを使ってPythonからphpへ値を渡す
暗号化した値の渡し方の例です
`urllib`モジュールを使います
```diff_python:encryption.py
import base64
import rsa
+ import urllib.request, urllib.parse

text = "hello world!" # 暗号化する文字列
pub_key_path = "./public_key.pem" # 公開鍵のパス

text_bytes = bytes(text, encoding="utf-8")
with open(pub_key_path, "rb") as f:
    public_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())

cipher = rsa.encrypt(text_bytes, public_key)
cipher_encode = base64.b64encode(cipher)
cipher_str = cipher_encode.decode("utf-8")

- print(cipher_str) # 暗号化された文字列

+ URL = "http://localhost/decoding.php" # 復号化するPHPのURL
+ data = {"cipher": cipher_str} # 暗号化した文字をphp側に送信
+ post_data = urllib.parse.urlencode(data).encode("utf-8")
+ with urllib.request.urlopen(URL, data=post_data) as re:
+    print(re.read().decode("utf-8")) # 復号化した文字列が返ってくる
```

```diff_php:decoding.php
<?php
+ if (!isset($_POST['cipher'])) {
+    echo 'No cipher';
+    exit;
+ }

- $cipher = '{Python側で出力された暗号化された文字列}';
+ $cipher = $_POST['cipher'];
$privateKeyPath = './private_key.pem'; // 秘密鍵のパス
$privateKey = file_get_contents($privateKeyPath);

$cipherDecode = base64_decode($cipher);
openssl_private_decrypt($cipherDecode, $decipher, $privateKey);

echo $decipher.PHP_EOL;
?>
```

# 参考サイト
- [公開鍵暗号方式でphpを使って暗号化と復号を試した](https://yuzu441.hateblo.jp/entry/2018/11/01/213255)
- [RSA暗号と電子署名](https://www.python.ambitious-engineer.com/archives/2042)
- [[Python] HTTP通信でGetやPostを行う](https://www.yoheim.net/blog.php?q=20160204)