0

I'm using a RSA to encrypt my Steam password and then login to my steam account, but i always get a {success:false} message after a try, this is the javascript code that steam is using for their login system :

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);
    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);
}
}

And i tried to rewrite in Java, but i have failed:

import java.math.*;

public class RSA {

private int length2;
private BigInteger modulus;
private BigInteger exponent;

public RSA(BigInteger modulus, BigInteger exponent){
  this.modulus = modulus;
  this.exponent = exponent;
  this.length2 = (modulus.bitLength()+7)>>3;
}

public byte[] pkcs1pad2(byte[] data){
  int n = this.length2;
  byte[] bytes = new byte[n];
  int i = data.length - 1;
  while (i >= 0 && n > 11)
  {
     bytes[--n] = data[i--];
  }
  bytes[--n] = 0;

  while (n > 2)
  {
     bytes[--n] = 0x01;
  }

  bytes[--n] = 0x2;
  bytes[--n] = 0;

  return bytes;
}
public String encrypt(String text){
  if (text.length() > this.length2)
  {
     System.out.println("RSA Encrypt: Message is too big!");
  }

  BigInteger m = new BigInteger(this.pkcs1pad2(text.getBytes()));
  if (m.intValue() == 0)
  {
     return null;
  }

  BigInteger c = m.modPow(exponent, modulus);
  if (c.intValue() == 0)
  {
     return null;
  }

  String result = c.toString(16);
  if ((result.length() & 1) == 0)
  {
     return result;
  }

  return "0" + result;
}
}

I provide the modulus and exponent as new BigInteger(string,16) And then i use apache libraries to Hex decode and Base64 encode.

Any ideas where the problem could be? I'm not really experienced in RSA.

Second code i used but didn't work either :

import java.math.BigInteger;

import java.security.KeyFactory;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

public class RSA {

private BigInteger modulus;
private BigInteger exponent;
private Cipher cipher;
private RSAPublicKeySpec pubkeyspec;
private KeyFactory factory;
private RSAPublicKey pubkey;

public RSA(BigInteger modulus, BigInteger exponent) throws Exception {
    this.modulus = modulus;
    this.exponent = exponent;
    this.init();
}

private void init() throws Exception {
    this.factory = KeyFactory.getInstance("RSA");
    this.pubkeyspec = new RSAPublicKeySpec(this.modulus, this.exponent);
    this.pubkey = (RSAPublicKey) factory.generatePublic( this.pubkeyspec );

    this.cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
    this.cipher.init(Cipher.ENCRYPT_MODE, this.pubkey );

}

public byte[] encrypt(byte[] data) throws Exception {
    return this.cipher.doFinal(data);
}

public String encrypt(String data) throws Exception {
    return new String(this.encrypt(data.getBytes()));
}
}
Jiří Velek
  • 153
  • 2
  • 12

1 Answers1

0

You should be able to use Cipher.getInstance("RSA/ECB/PKCS1Padding") instead of doing the padding yourself. That should significantly ease your development.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Could you check if that is enough? – Maarten Bodewes May 31 '14 at 22:06
  • I tried crypting with cipher too, but that didn't work too. I also tried running the javascript code, and it gives other result each time i run it. Unlike java, Java gives me the same result over and over again. – Jiří Velek Jun 01 '14 at 13:42
  • Are you sure you did not try with `Signature`? The code is *supposed* to create a different result each time. Any proper `Cipher` implementation should certainly do the same for `"PKCS1Padding"`. – Maarten Bodewes Jun 01 '14 at 14:56
  • I have rewritten the code, so it encrypts it with Cipher, but still does not work, but what do you mean by the `Signature` what it does it do and how do I use it? – Jiří Velek Jun 01 '14 at 15:20
  • You said that Cipher returned the same result, which should never be true for PKCS1Padding. A Signature with PKCS1Padding however should return the same output for the same input, hence I mentioned it. Could you show the Cipher code you tried with Java? – Maarten Bodewes Jun 01 '14 at 15:22
  • With the same result, i meant the first code with the modPow. – Jiří Velek Jun 01 '14 at 15:30
  • OK, but now you should get different results each time. Note that you should base 64 encode your result as you do in JavaScript, *never ever* do `new String(byte[])` if the byte array is the result of a cipher operation! – Maarten Bodewes Jun 01 '14 at 16:01
  • I encode it in my second file(using apache libraries) and send to the specific URL, but I still get an error `{"success":false}`, steam provides nothing about what did I do wrong. – Jiří Velek Jun 01 '14 at 16:07
  • `data.getBytes()` does not specify a character encoding, that may still be wrong. I'm not sure about the endianess of the JS encryption either. Most crypto experts don't supply any error on invalid signatures as it may give clues to attackers. – Maarten Bodewes Jun 01 '14 at 16:39
  • Which encoding should I use then? I can pass it as an argument. – Jiří Velek Jun 01 '14 at 17:27
  • The same one as in JavaScript, which is: [inconclusive but possibly UTF-16](http://stackoverflow.com/questions/11141136/default-javascript-character-encoding) - I cannot see how your `encrypt` function is called from JavaScript though. – Maarten Bodewes Jun 01 '14 at 17:32
  • In JavaScript it's : `RSA.encrypt(data, publickey);` where the publickey is generated by `RSA.getPublicKey(modulus, exponent)`. – Jiří Velek Jun 01 '14 at 18:31