40

公開鍵暗号を使用して、JavaScript で暗号化し、PHP で復号化したいと考えています。これを達成できるライブラリを見つけようとしていますが、問題があります。

私は現在openpgpjsを見ていますが、すべてのブラウザーでサポートが必要であり、テストページでさえ、サポートされているブラウザー (Google Chrome) としてリストされている唯一のエラーがあります。

最終目標に関する注意事項:

TCP 接続はすでに SSL によって保護されています。この保護層の主な目的は、意図的または意図的でない Web サーバーのログ記録、クラッシュ ダンプなどから防御することです。

PHP 側では、一時的な秘密鍵が生成されます (短時間で期限切れになります)。呼び出し元 (Javascript) は、有効期限が切れたときに新しい公開鍵を要求する責任があります。秘密鍵の有効期限が切れる理由は、秘密鍵を保存するサーバーが後で侵害された場合に、ログに記録された暗号化されたデータの復号化を防ぐためです。

サーバー侵害のシナリオ: 誰かが、データベース サーバーを除くすべてのマシンのバックアップを手に入れます (ユーザーとパスワードを見つけたとしても、ファイアウォールが原因でデータベースにアクセスできません)。ログデータを暗号化した秘密鍵は存在しないため、攻撃者は何もできません。

4

5 に答える 5

43

ログインページにも同様のものを使用しました。PHP で復号化できる特定の公開鍵情報 (N, e) を使用して、ログイン資格情報を暗号化します。

の一部である次のファイルを使用しますJSBN

  • jsbn.js- 大きな整数を扱う
  • rsa.js- RSA 暗号化のみ (jsbn.js を使用)
  • rng.js- 基本エントロピーコレクター
  • prng4.js- ARC4 RNG バックエンド

データを暗号化するには:

$pk = '-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----';
$kh = openssl_pkey_get_private($pk);
$details = openssl_pkey_get_details($kh);

function to_hex($data)
{
    return strtoupper(bin2hex($data));
}

?>
<script>
var rsa = new RSAKey();
rsa.setPublic('<?php echo to_hex($details['rsa']['n']) ?>', '<?php echo to_hex($details['rsa']['e']) ?>');

// encrypt using RSA
var data = rsa.encrypt('hello world');
</script>

これは、送信されたデータをデコードする方法です。

$kh = openssl_pkey_get_private($pk);
$details = openssl_pkey_get_details($kh);
// convert data from hexadecimal notation
$data = pack('H*', $data);
if (openssl_private_decrypt($data, $r, $kh)) {
   echo $r;
}
于 2012-09-25T03:50:29.090 に答える
24

node-rsa を確認してください。

それはnode.jsモジュールです

このモジュールは、OpenSSL から RSA 公開鍵ルーチンへのアクセスを提供します。サポートは、RSAES-OAEP と公開鍵による暗号化、秘密鍵による復号化に限定されます。

たぶん、ブラウザで実行するように移植できます。

アップデート

javascript 用の RSA クライアント側ライブラリ: (pidcrypt は正式に廃止され、ウェブサイト ドメインの有効期限が切れています。含まれている pidcrypt と同じライブラリを含む @jack の回答を参照してください)https://www.pidder.com/pidcrypt/?page=rsa

PHP サーバー側コンポーネント: http://phpseclib.sourceforge.net/

幸運を!

于 2012-09-17T10:48:32.157 に答える
16

RSA の実装には注意してください。実際、おそらく RSA をまったく使用すべきではありません。(代わりに libsodium を使用してください! )

ライブラリ (たとえば、PHP の OpenSSL 拡張機能を直接使用したり、最近まで使用していたもの) を使用している場合でも、Zend\Cryptうまくいかないことがたくさんあります。特に:

  • デフォルトの PKCS1v1.5 パディング(多くの場合、サポートされている唯一のパディング モード) は、パディング オラクルと呼ばれる選択暗号文攻撃のクラスに対して脆弱です。これは Daniel Bleichenbacher によって最初に発見されました。1998年。
  • RSA は大きなメッセージの暗号化には適していないため、実装者がよく行うことは、長いメッセージを取得して固定サイズのブロックに分割し、各ブロックを個別に暗号化することです。これは遅いだけでなく、対称鍵暗号化の恐ろしい ECB モードに似ています。

Libsodium を使用する最善の方法

このルートに進む前に、 「 JavaScript 暗号化は有害であると見なされる」を数回読むことをお勧めします。しかし、そうは言っても...

  1. HSTS および HPKP で TLSv1.2 を使用します。できれば ChaCha20-Poly1305 および/または AES-GCM と ECDSA-P256 証明書を使用します (重要: IETF が Curve25519 および Ed25519 と名付けた場合は、代わりにそれに切り替えてください)。
  2. プロジェクトにlibsodium.jsを追加します。
  3. crypto_box_seal()クライアント側でメッセージを暗号化するために公開鍵とともに使用します。
  4. PHP では\Sodium\crypto_box_seal_open()、対応する公開鍵の秘密鍵と共に使用して、メッセージを復号化します。

この問題を解決するには、RSA を使用する必要があります。

しないでください。楕円曲線暗号は、サイドチャネルなしで実装するのがより速く、より簡単で、はるかに簡単です。ほとんどのライブラリは、すでにこれを行っています。(リボナトリウム!)

でもどうしてもRSAを使いたい!

よし、これらの推奨事項に従ってください。間違いを犯したとき ( SaltStack がしたように)、暗号化が役に立たなくなったときに、StackOverflow に泣かないでください。

シンプルで簡単な RSA 暗号化を提供することを目的とした 1 つのオプション (補完的な JavaScript 実装は付属していません。求めないでください) は、paragonie/easyrsaです。

  • PKCS1v1.5の代わりにMGF1+SHA256 で RSA-OAEP を使用することにより、パディング オラクルを回避します。
  • 巧妙なプロトコル設計により、ECB モードを回避します。

EasyRSA 暗号化プロトコル

  1. EasyRSA は、対称キー暗号化 (AES 経由) 用にランダムな 128 ビット キーを生成します。
  2. 平文メッセージはdefuse/php-encryption で暗号化されます。
  3. AES キーは、正しいモード (上記) を使用して、phpseclibによって提供される RSA で暗号化されます。
  4. この情報は単純な文字列 (チェックサム付き) としてまとめられます。

しかし、実際には、公開鍵暗号化の有効な使用例を見つけた場合は、代わりに libsodium が必要です。

おまけ: JavaScript による暗号化、PHP による復号化

この目標を達成するために、ナトリウムプラスを使用します。(この投稿から採用。)

const publicKey = X25519PublicKey.from('fb1a219011c1e0d17699900ef22723e8a2b6e3b52ddbc268d763df4b0c002e73', 'hex');

async function sendEncryptedMessage() {
    let key = await getExampleKey();
    let message = $("#user-input").val();
    let encrypted = await sodium.crypto_box_seal(message, publicKey);
    $.post("/send-message", {"message": encrypted.toString('hex')}, function (response) {
        console.log(response);
        $("#output").append("<li><pre>" + response.message + "</pre></li>");
    });
}

次に、一致する PHP コード:

<?php
declare(strict_types=1);
require 'vendor/autoload.php'; // Composer
header('Content-Type: application/json');
$keypair = sodium_hex2bin(
    '0202040a9fbf98e1e712b0be8f4e46e73e4f72e25edb72e0cdec026b370f4787' .
    'fb1a219011c1e0d17699900ef22723e8a2b6e3b52ddbc268d763df4b0c002e73'
);

$encrypted = $_POST['message'] ?? null;
if (!$encrypted) {
    echo json_encode(
        ['message' => null, 'error' => 'no message provided'],
        JSON_PRETTY_PRINT
    );
    exit(1);
}
$plaintext = sodium_crypto_box_seal_open(sodium_hex2bin($encrypted), $keypair);

echo json_encode(
    ['message' => $plaintext, 'original' => $encrypted],
    JSON_PRETTY_PRINT
);
于 2015-12-03T05:38:14.103 に答える
1

pidCrypt (js) およびphpseclib (php)の RSA 使用例。

この作業例では秘密鍵を再利用しないでください。

pidCrypt 暗号化

//From the pidCrypt example sandbox
function certParser(cert) {
    var lines = cert.split('\n');
    var read = false;
    var b64 = false;
    var end = false;
    var flag = '';
    var retObj = {
    };
    retObj.info = '';
    retObj.salt = '';
    retObj.iv;
    retObj.b64 = '';
    retObj.aes = false;
    retObj.mode = '';
    retObj.bits = 0;
    for (var i = 0; i < lines.length; i++) {
        flag = lines[i].substr(0, 9);
        if (i == 1 && flag != 'Proc-Type' && flag.indexOf('M') == 0)//unencrypted cert?
        b64 = true;
        switch (flag) {
            case '-----BEGI':
                read = true;
                break;
            case 'Proc-Type':
                if (read)retObj.info = lines[i];
                break;
            case 'DEK-Info:':
                if (read) {
                    var tmp = lines[i].split(',');
                    var dek = tmp[0].split(': ');
                    var aes = dek[1].split('-');
                    retObj.aes = (aes[0] == 'AES') ? true : false;
                    retObj.mode = aes[2];
                    retObj.bits = parseInt(aes[1]);
                    retObj.salt = tmp[1].substr(0, 16);
                    retObj.iv = tmp[1];
                }
                break;
            case '':
                if (read)b64 = true;
                break;
            case '-----END ':
                if (read) {
                    b64 = false;
                    read = false;
                }
                break;
                default : if (read && b64)retObj.b64 += pidCryptUtil.stripLineFeeds(lines[i]);
        }
    }
    return retObj;
}

var strCreditCardPublicKey="-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC\/tI7cw+gnUPK2LqWp50XboJ1i\njrLDn+4\/gPOe+pB5kz4VJX2KWwg9iYMG9UJ1M+AeN33qT7xt9ob2dxgtTh7Mug2S\nn1TLz4donuIzxCmW+SZdU1Y+WNDINds194hWsAVhMC1ClMQTfldUGzQnI5sXvZTF\nJWp\/9jheCNLDRIkAnQIDAQAB\n-----END PUBLIC KEY-----\n";

var objParams=certParser(strCreditCardPublicKey);
var binaryPrivateKey=pidCryptUtil.decodeBase64(objParams.b64);

var rsa=new pidCrypt.RSA();

var asn=pidCrypt.ASN1.decode(pidCryptUtil.toByteArray(key));
var tree=asn.toHexTree();
rsa.setPublicKeyFromASN(tree);

var strHexSensitiveDataEncrypted=rsa.encrypt("4111111111111111");

var strBase64SensitiveDataEncrypted=pidCryptUtil.fragment(pidCryptUtil.encodeBase64(pidCryptUtil.convertFromHex(strHexSensitiveDataEncrypted)), 64))

console.log(strBase64SensitiveDataEncrypted);

.

phpseclib 復号化

require_once("Crypt/RSA.php");

function decrypt($strBase64CipherText)
{
    //CRYPT_RSA_MODE_INTERNAL is slow
    //CRYPT_RSA_MODE_OPENSSL is fast, but requires openssl to be installed, configured and accessible.
    define("CRYPT_RSA_MODE", CRYPT_RSA_MODE_INTERNAL);

    $rsa=new Crypt_RSA();


    //$strPrivateKey=file_get_contents("private.pem");
    //This private key is for example purposes
    //DO NOT REUSE
    $strPrivateKey="-----BEGIN RSA PRIVATE KEY-----
        MIICXQIBAAKBgQDBNHK7R2CCYGqljipbPoj3Pwyz4cF4bL5rsm1t8S30gbEbMnKn
        1gpzteoPlKp7qp0TnsgKab13Fo1d+Yy8u3m7JUd/sBrUa9knY6dpreZ9VTNul8Bs
        p2LNnAXOIA5xwT10PU4uoWOo1v/wn8eMeBS7QsDFOzIm+dptHYorB3DOUQIDAQAB
        AoGBAKgwGyxy702v10b1omO55YuupEU3Yq+NopqoQeCyUnoGKIHvgaYfiwu9sdsM
        ZPiwxnqc/7Eo6Zlw1XGYWu61GTrOC8MqJKswJvzZ0LrO3oEb8IYRaPxvuRn3rrUz
        K7WnPJyQ2FPL+/D81NK6SH1eHZjemb1jV9d8uGb7ifvha5j9AkEA+4/dZV+dZebL
        dRKtyHLfbXaUhJcNmM+04hqN1DUhdLAfnFthoiSDw3i1EFixvPSiBfwuWC6h9mtL
        CeKgySaOkwJBAMSdBhn3C8NHhsJA8ihQbsPa6DyeZN+oitiU33HfuggO3SVIBN/7
        HmnuLibqdxpnDOtJT+9A+1D29TkNENlTWgsCQGjVIC8xtFcV4e2s1gz1ihSE2QmU
        JU9sJ3YeGMK5TXLiPpobHsnCK8LW16WzQIZ879RMrkeDT21wcvnwno6U6c8CQQCl
        dsiVvXUmyOE+Rc4F43r0VRwxN9QI7hy7nL5XZUN4WJoAMBX6Maos2Af7NEM78xHK
        SY59+aAHSW6irr5JR351AkBA+o7OZzHIhvJfaZLUSwTPsRhkdE9mx44rEjXoJsaT
        e8DYZKr84Cbm+OSmlApt/4d6M4YA581Os1eC8kopewpy
        -----END RSA PRIVATE KEY-----
    ";
    $strPrivateKey=preg_replace("/[ \t]/", "", $strPrivateKey);//this won't be necessary when loading from PEM


    $rsa->loadKey($strPrivateKey);

    $binaryCiphertext=base64_decode($strBase64CipherText);

    $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
    $strBase64DecryptedData=$rsa->decrypt($binaryCiphertext);

    return base64_decode($strBase64DecryptedData);
}

//The pidCrypt example implementation will output a base64 string of an encrypted base64 string which contains the original data, like this one:
$strBase64CipherText="JDlK7L/nGodDJodhCj4uMw0/LW329HhO2EvxNXNUuhe+C/PFcJBE7Gp5GWZ835fNekJDbotsUFpLvP187AFAcNEfP7VAH1xLhhlB2a9Uj/z4Hulr4E2EPs6XgvmLBS3MwiHALX2fES5hSKY/sfSUssRH10nBHHO9wBLHw5mRaeg=";

$binaryDecrypted=decrypt($strBase64CipherText);

//should output '4111111111111111'
var_export($binaryDecrypted);
于 2012-09-18T16:55:34.783 に答える
0

これは、対称 (秘密鍵) 暗号化システムであるTiny Encryption Algorithmに基づいています。それでも軽いので重宝しそうです。

これは現在、http: //babelfish.nl/Projecten/JavascriptPhpEncryptionにあります。

于 2012-09-24T19:56:58.480 に答える