将 RSA 加密从 Javascript 转换为 PHP [英] Converting RSA Encryption from Javascript to PHP

查看:59
本文介绍了将 RSA 加密从 Javascript 转换为 PHP的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试登录使用 Javascript RSA 加密明文密码的网站 (Steam),以便将 POST 请求中的密文作为参数发送.我无法正确地将 Javascript RSA 从 Javascript 转换为 PHP.

I am trying to login to a website (Steam) which encrypts the plaintext password using Javascript RSA as to send the ciphertext in the POST request as a parameter. I am having trouble correctly converting the Javascript RSA from Javascript to PHP.

当我尝试将用我的任何 PHP 脚本创建的密文密码发送到网站时,我得到一个错误的登录信息,表明我的加密过程中某处不正确.

When I attempt to send the ciphertext password created with any of my PHP script's to the website, I get an incorrect login indicating that something is incorrect somewhere in my encryption process.

当从浏览器发送实际请求并使用 Fiddler 记录请求时,模数的位长始终与密文的位长相同.这也可以从 Javascript 函数 pkcs1pad2 推导出来.这是我在尝试检查加密是否正确时寻找的标准之一.

When sending an actual request from the browser and recording the request with Fiddler, the bit length of the modulus was always the same as that of the ciphertext. This can also be deduced from the Javascript function pkcs1pad2. This was one of the criteria that I looked for when trying to check if the encryption was correct.

使用相同的公钥和明文不会总是产生与使用 pkcs1 时相同的密文,随机字节填充到明文的开头,直到长度与模数相同.因此无法与通过浏览器获取的正确密文进行比较.

Using the same public key and plaintext will not always result in the same ciphertext as when using pkcs1, random bytes are padded onto the start of the plaintext until the length is the same as the modulus. Therefore it is not possible to compare against a correct ciphertext obtained through the browser.

Javascript 的 modPowInt($exponent, $modulus) 和 PHP 的 modPow($exponent, $modulus) 执行不同的计算,因为 PHP RSA (2) 不起作用,尽管它似乎正是与 Javascript RSA 相同吗?

Do Javascript's modPowInt($exponent, $modulus) and PHP's modPow($exponent, $modulus) perform different calculations as PHP RSA (2) doesn't work although it seems to be exactly the same as Javascript RSA?


Javascript RSA - 需要转换为 PHP 的内容

Javascript RSA - What needs to be converted to PHP

var RSAPublicKey = function($modulus_hex, $encryptionExponent_hex) {
    this.modulus = new BigInteger( $modulus_hex, 16);
    this.encryptionExponent = new BigInteger( $encryptionExponent_hex, 16);
};

var Base64 = {
    base64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    encode: function($input) {
        if (!$input) {
            return false;
        }
        var $output = "";
        var $chr1, $chr2, $chr3;
        var $enc1, $enc2, $enc3, $enc4;
        var $i = 0;
        do {
            $chr1 = $input.charCodeAt($i++);
            $chr2 = $input.charCodeAt($i++);
            $chr3 = $input.charCodeAt($i++);
            $enc1 = $chr1 >> 2;
            $enc2 = (($chr1 & 3) << 4) | ($chr2 >> 4);
            $enc3 = (($chr2 & 15) << 2) | ($chr3 >> 6);
            $enc4 = $chr3 & 63;
            if (isNaN($chr2)) $enc3 = $enc4 = 64;
            else if (isNaN($chr3)) $enc4 = 64;
            $output += this.base64.charAt($enc1) + this.base64.charAt($enc2) + this.base64.charAt($enc3) + this.base64.charAt($enc4);
        } while ($i < $input.length);
        return $output;
    },
    decode: function($input) {
        if(!$input) return false;
        $input = $input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        var $output = "";
        var $enc1, $enc2, $enc3, $enc4;
        var $i = 0;
        do {
            $enc1 = this.base64.indexOf($input.charAt($i++));
            $enc2 = this.base64.indexOf($input.charAt($i++));
            $enc3 = this.base64.indexOf($input.charAt($i++));
            $enc4 = this.base64.indexOf($input.charAt($i++));
            $output += String.fromCharCode(($enc1 << 2) | ($enc2 >> 4));
            if ($enc3 != 64) $output += String.fromCharCode((($enc2 & 15) << 4) | ($enc3 >> 2));
            if ($enc4 != 64) $output += String.fromCharCode((($enc3 & 3) << 6) | $enc4);
        } while ($i < $input.length);
        return $output;
    }
};

var Hex = {
    hex: "0123456789abcdef",
    encode: function($input) {
        if(!$input) return false;
        var $output = "";
        var $k;
        var $i = 0;
        do {
            $k = $input.charCodeAt($i++);
            $output += this.hex.charAt(($k >> 4) &0xf) + this.hex.charAt($k & 0xf);
        } while ($i < $input.length);
        return $output;
    },
    decode: function($input) {
        if(!$input) return false;
        $input = $input.replace(/[^0-9abcdef]/g, "");
        var $output = "";
        var $i = 0;
        do {
            $output += String.fromCharCode(((this.hex.indexOf($input.charAt($i++)) << 4) & 0xf0) | (this.hex.indexOf($input.charAt($i++)) & 0xf));
        } while ($i < $input.length);
        return $output;
    }
};

var RSA = {

    getPublicKey: function( $modulus_hex, $exponent_hex ) {
        return new RSAPublicKey( $modulus_hex, $exponent_hex );
    },

    encrypt: function($data, $pubkey) {
        if (!$pubkey) return false;
        $data = this.pkcs1pad2($data,($pubkey.modulus.bitLength()+7)>>3);
        if(!$data) return false;
        $data = $data.modPowInt($pubkey.encryptionExponent, $pubkey.modulus);
        if(!$data) return false;
        $data = $data.toString(16);
        if(($data.length & 1) == 1)
            $data = "0" + $data;
        return Base64.encode(Hex.decode($data));
    },

    pkcs1pad2: function($data, $keysize) {
        if($keysize < $data.length + 11)
            return null;
        var $buffer = [];
        var $i = $data.length - 1;
        while($i >= 0 && $keysize > 0)
            $buffer[--$keysize] = $data.charCodeAt($i--);
        $buffer[--$keysize] = 0;
        while($keysize > 2)
            $buffer[--$keysize] = Math.floor(Math.random()*254) + 1;
        $buffer[--$keysize] = 2;
        $buffer[--$keysize] = 0;
        return new BigInteger($buffer);
    }
};


PHP RSA (1)这是我解决加密问题的初步尝试.密文的Bit Length与Modulus的Bit Length一致(2048.但是密文导致登录错误.

PHP RSA (1) This was my initial attempt at solving the encryption issue. The Bit Length of the ciphertext is consistant with that of the Modulus (2048. The ciphertext however, resulted in an incorrect login.

include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';

function encrypt($data, $mod, $exp)
{
    $rsa = new Crypt_RSA();
    $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
    $rsa->publicExponent = new Math_BigInteger($exp, 16);
    $rsa->modulus = new Math_BigInteger($mod, 16);
    $rsa->publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1;
    $rsa->loadKey($rsa->getPublicKey());
    return urlencode(base64_encode($rsa->encrypt($data)));
}


PHP RSA (2)我试图将 Javascript RSA 脚本转换为 PHP.位长与模数不一致(2472不是2048)

PHP RSA (2) I tried to convert the Javascript RSA script to PHP. The Bit Length is inconsistent with that of the Modulus (2472 not 2048)

include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';

function encrypt($data, $mod, $exp){
    $mod = new Math_BigInteger($mod,16);
    $exp = new Math_BigInteger($exp,16);
    if($exp == null || $mod == null) return false;
    $data = pkcs1pad2($data, (strlen($mod->toBits())+7)>>3);
    if($data == null) return false;
    $data = $data->modPow($exp,$mod);
    if($data == null) return false;
    $data = $data->toString();
    if((strlen($data) & 1) == 1)
        $data = "0" . $data;
    return urlencode(base64_encode(hex2bin($data)));
}

function pkcs1pad2($data, $keysize){
    if($keysize < strlen($data) + 11)
        return null;
    $buffer = array();
    $i = strlen($data)-1;
    while($i >= 0 && $keysize > 0)
        $buffer[--$keysize] = $data[$i--];
    $buffer[--$keysize] = 0;
    while($keysize > 2)
        $buffer[--$keysize] = rand(0,255);
    $buffer[--$keysize] = 2;
    $buffer[--$keysize] = 0;
    return new Math_BigInteger(bin2hex(implode('',$buffer)), 16);
}


PHP RSA (3)这是我的代码的最后一次迭代.我尝试填充丢失的位以使位长与模数(2048)的位长相同并成功.尽管如此,密文密码仍然被网站认为是不正确的.

PHP RSA (3) This is the final iteration of my code. I tried to pad missing bits to make the Bit Length the same as that of the Modulus (2048) and succeeded. The ciphertext password is still considered incorrect by the website though.

include('Crypt/RSA.php');
include('Math/BigInteger.php');

function hex_to_binary($hex) {
    $binary = '';
    for ($i = 0; $i < strlen($hex); $i += 2) {
        $hexChunk = substr($hex, $i, 2);
        $intChunk = hexdec($hexChunk);
        $binaryChunk = decbin($intChunk);
        $binaryChunk = str_pad($binaryChunk, 8, '0', STR_PAD_LEFT);
        $binary .= $binaryChunk;
    }
    return $binary;
}

function bytes_to_binary($bytes) {
    $binary = '';
    foreach($bytes as $integer) {
        $byte = decbin($integer);
        $byte = str_pad($byte, 8, '0', STR_PAD_LEFT);
        $binary .= $byte;
    }
    return $binary;
}

function binary_to_text($binary) {
    $text = '';
    $binaryLength = strlen($binary);
    for ($i = 0; $i < $binaryLength; $i += 8) {
        $binaryChunk = substr($binary, $i, 8);
        $characterCode = bindec($binaryChunk);
        $character = chr($characterCode);
        $text .= $character;
    }
    return $text;
}

function getPublicKey($modulusHex, $exponentHex) {
    $publicKey = Array(
        'modulus' => $modulusHex,
        'exponent' => $exponentHex
    );
    return $publicKey;
}

function pkcs1pad2($data, $publicKey) {
    // Convert Modulus from Hex to Binary
    $modulusBinary = hex_to_binary($publicKey['modulus']);
    // Get Bytes of Modulus
    $modulusInteger = new Math_BigInteger($modulusBinary, 2);
    $modulusBytes = ceil(strlen($modulusInteger->toBits()) / 8);
    // Bytes in the Modulus must be 11 Bytes longer than Bytes in the Password (UTF-8 => 8 Bytes per Character)
    if($modulusBytes < strlen($data) + 11) {
        // Otherwise Encryption is impossible so Return Null
        return null;
    };
    // Array to Store Sequence of Bytes in the Padded Password
    $buffer = array();
    // Variables to Hold Current Position of Bytes and Characters
    $currentModulusByte = $modulusBytes;
    $currentDataCharacter = strlen($data) - 1;
    // Insert Password into End of Buffer
    while($currentDataCharacter >= 0 && $currentModulusByte > 0) {
        $buffer[--$currentModulusByte] = ord($data[$currentModulusByte--]);
    };
    // Insert 0 as the Next Last Value of Buffer
    $Buffer[--$currentModulusByte] = 0;
    // Insert Random Bytes into Buffer until the First 2 Bytes
    while($currentModulusByte > 2) {
        $buffer[--$currentModulusByte] = rand(1,255);
    };
    // Insert 0 and 2 as the First 2 Bytes in the Sequence
    $buffer[--$currentModulusByte] = 2;
    $buffer[--$currentModulusByte] = 0;
    // Math_BigInteger() doesn't accept an Array of Bytes so convert it to a Binary string
    $paddedModulusBinary = bytes_to_binary($buffer);
    // Convert Binary to BigInteger using a Base 2
    $paddedModulusInteger = new Math_BigInteger($paddedModulusBinary, 2);
    return $paddedModulusInteger;
}

// Copy of the Encrypt function
function encrypt($data, $publicKey) {
    // Make Sure that the Public Key is not Null
    if(!$publicKey) {
        return false;
    };
    // Pad the Data for Encryption
    $paddedData = pkcs1pad2($data, $publicKey);
    // Make Sure that the Padded Data is not Null
    if(!$paddedData) {
        return false;
    };
    // Encrypt the Padded Data using the Public Key
    $exponentBinary = hex_to_binary($publicKey['exponent']);
    $exponentBigInt = new Math_BigInteger($exponentBinary, 2);
    $modulusBinary = hex_to_binary($publicKey['modulus']);
    $modulusBigInt = new Math_BigInteger($modulusBinary, 2);
    $encryptedData = $paddedData->modPow($exponentBigInt, $modulusBigInt);
    // Make Sure that the Encrypted Data is not Null
    if(!$encryptedData) {
        return false;
    }
    // Convert the Encrypted Data to Binary
    $encryptedBinaryData = $encryptedData->toBits();
    // Pad Empty Bits onto the Start of the Encrypted Binary Data
    $modulusBitLength = strlen($publicKey['modulus']) * 4;
    $encryptedBinaryData = str_pad($encryptedBinaryData, $modulusBitLength, '0', STR_PAD_LEFT);
    // Convert Binary to Text
    $textData = binary_to_text($encryptedBinaryData);
    // Encode Binary with Base64
    $base64EncodedData = base64_encode($textData);
    // Encode Base64 for Url
    $urlEncodedData = urlencode($base64EncodedData);
    return $urlEncodedData;
 }


任何帮助都将不胜感激.
这个问题过去曾出现过,但从未得到正确回答.希望这次我们能有更多的运气.

Any help at all would be much appreciated.
This question has come up in the past but has never been properly answered. Hopefully we will have some more luck this time.

推荐答案

我的第一个想法...你可能需要做 define('CRYPT_RSA_PKCS15_COMPAT', true).phpseclib - 您在 PHP 端使用的 - 使用 PKCS1 v2.0+ 中定义的填充技术,该技术与 PKCS1 v1.5 中描述的填充技术略有不同.在进行加密之前执行 define('CRYPT_RSA_PKCS15_COMPAT', true) 可能会有所帮助.

My first thought... you may need to do define('CRYPT_RSA_PKCS15_COMPAT', true). phpseclib - which you're using on the PHP side - uses a padding technique defined in PKCS1 v2.0+, which differs slightly from the padding technique described in PKCS1 v1.5. Doing define('CRYPT_RSA_PKCS15_COMPAT', true) before you do the encryption might help.

如果做不到这一点,可能值得尝试 $rsa->publicExponent = new Math_BigInteger($exp, -16)

Failing that, it might be worthwhile to try to do $rsa->publicExponent = new Math_BigInteger($exp, -16)

当我下班回家时,我会尝试更多地玩这个..

I'll try to play around with this some more when I get home from work..

这篇关于将 RSA 加密从 Javascript 转换为 PHP的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆