RSA Encryption in Pure PHP

Note: I didn’t really make these classes to be portable, so in the RSA_Handler class, there are a few things that I did my own way (mostly in the generate_keys and encrypt functions), but you should still be able to see how it works. If you want the most basic usage, use an RSA_keymaker object and the make_keys function. The function will return an array of keys (in number form) where $keys[0] is the public key, $keys[1] is the private key and $keys[2] is the modulo. The keys generated with the default function are around 1024 bits (a 310 digit modulo).

In the future I’ll probably tweak these so that they’re useable on any PHP build (after 4.0 or so).

EDIT: I have updated the code! It is now 100% portable and full functioning. The only thing I have left out is the ability to encrypt the private key, but there are several pure PHP implementations of AES, DES3, Blowfish etc. which can be used to encrypt a private key for storage. This is released under the GPL Licence. Do whatever you want with it, but know there’s no warranty express or implied. My work here is done 🙂

UPDATE: The code is now more efficient, and uses number of bits to determine key sizes (instead of number of digits). The version number is included in the code so you can always check to see if there is a newer version available (but don’t hold your breath)

IT’S NOW ON GITHUB!!! Please feel free to suggest your changes there and I’ll be happy to commit them. I’d love to spend more time on this but I’m swamped. Thanks to all the commenters!

Download in a text file

36 Responses to “RSA Encryption in Pure PHP”

  1. dob Says:

    I was looking for a way to encrypt data using RSA public keys from a web host. No hope for a PHP stuff but you did it! And more!..

    Thank you for that!

    Great job!

  2. Christian Lehmann Says:

    Hi. Thanks for your code. I’m trying to get this to work but I’m stuck. I added some debug-echos and found that I needed to uncomment the $maxed = false; and $mined=false; in make_prime to get some echoes at all. Any help is appreciated. Thanks.

    if($maxed && $thisdigit $thismin)
    $mined = false;

  3. watercrash Says:

    my host does not have the bcmath extension installed. could you use this instead?: tml#math_biginteger

    it does what bcmath does but only uses php, instead. well, it uses bcmath if it is available, for speed, but if it is not, it uses a pure-php implementation.


  4. Tester Says:

    Oops!… found the infinite loop at:

    while($thisdigit = $thismin)
    $thisdigit = ($thisdigit + rand(0,9)) % 10;

  5. Tester Says:

    while($thisdigit <= $thismax && $thisdigit >= $thismin) {
    $thisdigit = ($thisdigit + rand(0,9)) % 10;


    $thismax === 9
    $thismin === 0

  6. insid0r Says:

    As Tester says, if $thismin == 0 and $thismax == 9, the $thisdigit while loop would thrash for infinity. In haste, and because the primality check later modifies the number anyway (so it doesn’t really matter), I dropped some entropy but fixed the loop by changing:
    while($thisdigit = $thismin)
    while($thisdigit = $thismin)

    Likewise, a couple lines down, the primality check while loop would sometimes go on for far too long, so I essentially random-restarted (valid AI theory), by changing:
    if (++$tries > 2001) return make_prime($bits);
    (2001 is completely arbitrary, but works in my test cases. Also note ++$i is 1/3 faster than $i++, which matters for big loops.)

    With the above changes, the script generates keypairs in decent time, and self-rectifies if the original guess was nowhere near a prime. Since the problem space is fairly sparse, it’s better to guess again than to keep incrementing (at least, it seems to solve the problem much faster.)

  7. insid0r Says:

    Oops. The top of that should read (but was HTML-tag filtered out):
    I changed:
    while($thisdigit <= $thismax && $thisdigit >= $thismin)
    while($thisdigit < $thismax && $thisdigit >= $thismin)

  8. insid0r Says:

    Also return make_prime($bits); should become return $this->make_prime($bits);

    My bad.

  9. Tester Says:

    insid0r, for me, is the logic that’s wrong. This is my solution:

    while( ($thisdigit != 1 && $thisdigit != 3 && $thisdigit != 7 && $thisdigit != 9) || ($thisdigit < $thismin || $thisdigit > $thismax) )
    $thisdigit = ($thisdigit + rand(0,9)) % 10;
    while($thisdigit < $thismin || $thisdigit > $thismax)
    $thisdigit = ($thisdigit + rand(0,9)) % 10;

  10. Tester Says:

    This statement -in make_keys() function- will cause a too much short prime number… and invalid (de)crypt:

    while( substr($u, -16, 2) < (substr($v, -16, 2) + 2) && substr($u, -16, 2) > (substr($v, -16, 2) – 2) ) {
    //Make sure the 2 primes are at least 1 quadrillion numbers apart
    $v = $this->make_prime(intval($digits/2));

    The right way is:

    while( substr($u, -16, 2) < (substr($v, -16, 2) + 2) && substr($u, -16, 2) > (substr($v, -16, 2) – 2) ) {
    //Make sure the 2 primes are at least 1 quadrillion numbers apart
    $v = $this->make_prime(floor($bits/2) – $variant);

  11. Dan Says:

    Hey, just a little remark, in the one method with $chars = “….” you should be transforming this to a more unicode-friendly thing like using the dec/hex value instead of the chars themselves. $chars = “x1234x1235x1236…..” like this i mean.

    btw a hoster w/o common libs like bcmath,socks,curl,..enabled isnt worth a penny, sorry bro 😛

  12. Chad Says:

    Does anyone have a fully revised version of this class (with the changes above) that works?

  13. alan Says:


    some great ideas here:)

  14. Charles Says:

    I’ve merged in all the changes listed here and decryption still does not seem to work correctly.

    Any hints?

  15. Mat Says:

    Same here… this scripts doesn’t seem to work.

    PHP Notice: Undefined property: RSA_keymaker::$primes in rsa.php on line 149
    PHP Fatal error: Maximum execution time of 30 seconds exceeded in rsa.php on line 232

  16. Alan Says:


    I borrowed some code from this. Anyways, I rewrote you prime list init in the constructor. The new code is about six times faster and less memory intense. Anyways, here it is if interested:

    public function initPrimes2($max_prime = 20000) {
    if(is_null($this->primes)) {
    for ($i = 0; $i $num) {
    if(!$num) {
    $j = $i;

    for ($j += $i; $j primes[] = $num;

    New code:
    public function initPrimes($max_prime = 20000) {
    if(is_null($this->primes)) {
    //Add 2 and 3
    $this->primes[] = 2;
    $this->primes[] = 3;

    //Make list of numbers not divisible by 2 and 3
    $pc_list = array();
    $stop_at = $max_prime/6;
    for($i=1; $i $bool) {
    if(!$pc_list[$pc]) {
    $cur = $pc;
    $stop_at = $max_prime/(6*$cur);
    while($cur $bool) {
    if($bool) {
    $this->primes[] = $pc;

  17. Alan Says:


    I got your code working. The main problem I found (other then a few minor glitches such as inf. loop and a few typos) is the chars string in the “long_base_convert” function. Basically there are 95 printable standard ascii characters and the string is 145 chars. Some of the other chars require a second char index in order to use them. So, is you do a strlen() of the chars string, you get back 201 instead of 145. These extra chars are non-unique which breaks function.

    So, you can use just the 94 printable chars (can’t use space) and work in base 94. Simply change the base 145 to 94 in the function calls and change the chars string to:

    $chars = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP QRSTUVWXYZ_-+=!@#$%^*(){[}]|:,.?/`~&’;\”\\”

    Alternatively you can build a chars string user the chr() function. With this you can use the full ascii table and work in base 255. function make_char_list() {
    $segments = array(
    48, 57, //Numbers
    97, 122, //Lowercase
    65, 90, //Uppercase
    91, 96, //Symbols
    33, 47, //Symbols — Skip space (33)
    58, 64, //Symbols
    0, 31, //Beginning of table
    123, 255 //End of table

    $str = “”;
    for($i=0; $i < count($segments); $i+=2) {
    for($j = $segments[$i]; $j <= $segments[$i+1]; $j++) {
    $str .= chr($j);

    return $str;

    Just don't print out the encrypted data and treat it as binary. Also, ASCII index 0 is the null character, so, that index may want to be skipped if it causes a problem with string (not sure how php does it). Anyways, it seems to work for me so far.

    I have made quite a few modifications to the code. If you want my version, let me know where to send it.

  18. John Says:

    @Alan I would be interested in your modified version myself as I prefer to use this over
    phpseclib. Even tho I have full control over the Server, I won’t need the full features
    of phpseclib and would prefer a simple implementation. I can be reached at:
    mythdis-forumewfps (at) everonworlds (dot) com Cheers

  19. John Says:

    woops, forgot, make that (at ) (its one of my disposables to avoid
    spam 🙂

  20. emiller12345 Says:

    I was getting an error …
    Undefined property: RSA_keymaker::$primes
    …and found a simple correction:
    — rsa-classes.txt.orig 2011-12-12 13:31:51.757542662 -0500
    +++ rsa-classes.txt 2011-12-12 13:30:54.477542217 -0500
    @@ -144,7 +144,7 @@
    static $primes = null;

    function __construct() {
    – if(is_null($this->primes)) {
    + if(isset($this->primes)) {
    //Make $this->primes an array of all primes under 20,000
    //We will use this list to rule out the “easy” composite (non-prime) numbers

  21. Dusu Says:

    It has taken too long to call serialize(). It doesn’t seem to return. What shall I do?

  22. maina Says:

    actually i want a working version of these, kindly assist..erros pease me off.

  23. fauzie Says:

    Hallo Alan

    Please send your code to my email ..


  24. Redkin Says:

    So Alan are you sending your code to anyone?
    I would be interested too…
    mani . reini (at) gmail . com

  25. Dantali0n Says:

    looks great, will try to use it on my webserver this weeks.
    excited to see if I can get it to work.

    Many thanks in advance!

  26. Jerry Wickey Says:

    It works great with three changes.

    Great job Stevish ! Thank you ! ! The best explanation of RSA I’ve seen. It really helped to put all the pieces together for me.

    I couldn’t get it to work at first. I found a typo and three other problems. The biggest of which, by far, was the prime number generator. I tried to fix it, but gave up and wrote an alternate.

    By next week sometime. I will have a javasccript PHP library to make passing all data between the client and server easy to encrypt. It will have four functions, encrypt on the client side, encrypt on the server side, and decrypt client and server side. Plus an initialization to get the keys. I will offer these for download for anyone to use, because I encourage the ubiquitous use of encryption.

    Look for them by before Feb 2014. In the mean time, please check over my work. Please, post if you find any mistakes I made.


    Make these four changes to the code posted here, the test lines will work

    $text = “Peter Piper picked a peck of pickled peppers”;?
    $RSA = new RSA_Handler();?
    $keys = $RSA->generate_keypair(512);?
    $encrypted = $RSA->encrypt($text, $keys[0]);? $decrypted = $RSA->decrypt($encrypted, $keys[1]);?
    echo $decrypted; //Will print Peter Piper picked a peck of pickled peppers

    First reduce the number of bits to 512. 1024 takes too long. PHP often times out. I have a fix for this that will come out in the download next week. Look for it here. I will also post it in some programming forums, CodeCall and Stack Overflow.

    Change the test line:
    $keys = $RSA->generate_keypair(1024);
    $keys = $RSA->generate_keypair(512);

    This line is a typo:
    $v = $pm->make_prime(intval($digits/2));

    change the $pm to $his:
    $v = $this->make_prime(intval($digits/2));

    Some characters used in these two lines, were simply problem characters for some systems:
    $out .= $this->long_base_convert($cryptblock, 10, 145) . ” “;
    $block = $this->long_base_convert($block, 145, 10);

    change the 145 to 87
    $out .= $this->long_base_convert($cryptblock, 10, 87) . ” “;
    $block = $this->long_base_convert($block, 87, 10);

    and in the line:
    $chars = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP QRSTUVWXYZ_-+=!@#$%^*(){[}]|:,.?/`~•¤¶§Çüéâ àåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥ƒ áíóúñѪº¿¬½¼¡«»¯ßµ±÷;”;

    change to:
    $chars = “0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP QRSTUVWXYZ_-+=!@#$%^*(){[}]|:,.?/`~”;

    In these two lines 1024 bits is forced regardless of what value is passed to it in “$keys = $RSA->generate_keypair(1024);”
    function generate_keypair($bits=1024 ) {
    function make_keys($bits=1024, $u = false, $v = false) {

    remove “=1024″ just cut it right out.
    function generate_keypair($bits ) {
    function make_keys($bits, $u = false, $v = false) {

    Finally the big one. The prime number generator is broke.
    Replace the whole function or class method:
    function make_prime($bits) { . . . }
    with this method instead:
    function make_prime($bits) {
    $b= array(1,3,7,9);
    $n= ”;
    do {
    $t= microtime( true );
    $n= ”;
    while (strlen($n) microtime( true ) && ! $this->is_prime( $n))
    $n= substr( $n, 0, -10) . ( intval( substr( $n, -10)) + 2);
    }while ( ! $this->is_prime( $n));
    return $n;

    completely remove the function:
    function entropyarray($digits) { . . . }

    You don’t need it anymore.

    Would love your critique Stevish.

  27. Jerry Wickey Says:

    while (strlen($n) microtime( true ) && ! $this->is_prime( $n))

    should read

    while ($t + 1.1 > microtime( true ) && ! $this->is_prime( $n))

  28. Jerry Wickey Says:

    Check that. I copy and pasted completely wrong lines. Think I can get it right this time?

    The function make_prime($bits) should be:

    function make_prime($bits) {
    $b= array(1,3,7,9);
    $n= ”;
    do {
    $t= microtime( true );
    $n= ”;
    while (strlen($n) microtime( true ) && ! $this->is_prime( $n))
    $n= substr( $n, 0, -10) . ( intval( substr( $n, -10)) + 2);
    }while ( ! $this->is_prime( $n));
    return $n;

  29. Jerry Wickey Says:

    OK it wasn’t my fault. the great than character messed up the post. This time the code should post correctly. Stevish, If you could please delete and paste this in the appropriate place. in my original post.

    function make_prime($bits) {
    $b= array(1,3,7,9);
    $n= ”;
    do {
    $t= microtime( true );
    $n= ”;
    while ( floor( $bits / 3 ) > strlen($n))
    $n.= mt_rand(0,9);
    str_shuffle( $n );
    $n.= $b[ mt_rand(0,3) ];
    while ($t + 1.1 > microtime( true ) && ! $this->is_prime( $n)){
    $n= substr( $n, 0, -10) . ( intval( substr( $n, -10)) + 2);
    }while ( ! $this->is_prime( $n));
    return $n;

  30. Alex Says:


    There is someone who could give me a working code of this?

  31. Stevish Says:

    Well I haven’t had much time, but I threw this up onto github ( Thanks to all the commenters, and maybe we’ll be able to get this thing fixed up over at github now.

  32. blackiron Says:

    Well, nothing of this works!

    nice work, but it should work. especially if many people commented about mistakes.
    This is what i get:

    Strict Standards: Accessing static property RSA_keymaker::$primes as non static in /srv/users/serverpilot/apps/www/public/rsa_tests/rsa- in-php.php on line 162

    Notice: Undefined property: RSA_keymaker::$primes in /srv/users/serverpilot/apps/www/public/rsa_tests/rsa- in-php.php on line 162

    Strict Standards: Accessing static property RSA_keymaker::$primes as non static in /srv/users/serverpilot/apps/www/public/rsa_tests/rsa- in-php.php on line 184

    Fatal error: Maximum execution time of 30 seconds exceeded in /srv/users/serverpilot/apps/www/public/rsa_tests/rsa- in-php.php on line 245

  33. Barenca Says:

    This does not work!!!

  34. Chris Says:

    Please, this does not work. for the same reason above.. please who can help me to fix it ?

  35. John FENDER Says:

    Not working on php 4.4

  36. sathis Says:

    i need hai text in rsa encrypted code

Leave a Reply