简体   繁体   English

将RSA加密从Java转换为PHP

[英]Converting RSA Encryption from Javascript to 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. 我正在尝试登录一个网站(Steam),该网站使用Javascript RSA加密明文密码,以将POST请求中的密文作为参数发送。 I am having trouble correctly converting the Javascript RSA from Javascript to PHP. 我无法正确将Javascript RSA从Javascript转换为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. 当我尝试将用任何PHP脚本创建的密文密码发送到网站时,我得到的登录名不正确,表明加密过程中某处存在错误。

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. 从浏览器发送实际请求并使用Fiddler记录请求时,模数的位长始终与密文的位长相同。 This can also be deduced from the Javascript function pkcs1pad2. 这也可以从Javascript函数pkcs1pad2推导出来。 This was one of the criteria that I looked for when trying to check if the encryption was correct. 这是我尝试检查加密是否正确时要寻找的标准之一。

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. 与使用pkcs1时,使用相同的公钥和纯文本不一定会产生相同的密文,而是将随机字节填充到纯文本的开头,直到长度与模数相同为止。 Therefore it is not possible to compare against a correct ciphertext obtained through the browser. 因此,无法与通过浏览器获得的正确密文进行比较。

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的modPowInt($ exponent,$ modulus)和PHP的modPow($ exponent,$ modulus)会执行不同的计算吗,因为PHP RSA(2)虽然看起来和Javascript RSA完全一样,但不能工作吗?


Javascript RSA - What needs to be converted to PHP Javascript RSA-需要转换为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) This was my initial attempt at solving the encryption issue. PHP RSA(1)这是我最初尝试解决加密问题。 The Bit Length of the ciphertext is consistant with that of the Modulus (2048. The ciphertext however, resulted in an incorrect login. 密文的位长与模数(2048)一致。但是,密文导致登录错误。

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) I tried to convert the Javascript RSA script to PHP. PHP RSA(2)我试图将Javascript RSA脚本转换为PHP。 The Bit Length is inconsistent with that of the Modulus (2472 not 2048) 位长与模数不一致(2472而不是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) This is the final iteration of my code. PHP RSA(3)这是我代码的最终迭代。 I tried to pad missing bits to make the Bit Length the same as that of the Modulus (2048) and succeeded. 我尝试填充丢失的位,以使位长度与模数(2048)相同,并成功。 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. 希望这次我们能有更多的运气。

My first thought... you may need to do define('CRYPT_RSA_PKCS15_COMPAT', true) . 我的第一个想法...您可能需要做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. phpseclib(在PHP端使用)使用PKCS1 v2.0 +中定义的填充技术,该技术与PKCS1 v1.5中描述的填充技术略有不同。 Doing define('CRYPT_RSA_PKCS15_COMPAT', true) before you do the encryption might help. 在进行加密之前进行define('CRYPT_RSA_PKCS15_COMPAT', true)可能会有所帮助。

Failing that, it might be worthwhile to try to do $rsa->publicExponent = new Math_BigInteger($exp, -16) 失败的话,尝试执行$rsa->publicExponent = new Math_BigInteger($exp, -16)可能是值得的

I'll try to play around with this some more when I get home from work.. 当我下班回家时,我会尝试更多一些。

与其发布自己的解决方案, openssl_{public,private}_{encrypt,decrypt}考虑使用openssl_{public,private}_{encrypt,decrypt}函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM