This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/wwlib/rsacrypt.h

349 lines
8 KiB
C
Raw Permalink Normal View History

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RSACRYPT_H
#define RSACRYPT_H
#include <wwlib/int.h>
#include <wwlib/wwfile.h>
//#define SIMPLE_AND_SLOW_RSA
// Version identification string for OpenSSH identity files.
#define AUTHFILE_ID_STRING "SSH PRIVATE KEY FILE FORMAT 1.1\n"
//
// RSACrypt - Implementation of RSA using the Int class from wwlib
//
// For key generation I suggest you use OpenSSH (http://www.openssh.com/)
// There is a utility called "ssh-keygen", call:
//
// ssh-keygen [-b bits]
//
// If you generate the file on a UNIX system, be sure and FTP it in BINARY MODE!!!!
//
// You can then use 'Load_SSH_Keyset' on the identity file it produces.
//
// Note: Make sure you leave the keyphrase blank for the SSH keyset. I don't support
// decrypting that file!
//
// Notation:
// Public keys:
// n = product of two primes, p & q (p & q must remain secret)
// e = relatively prime to (p-1)(q-1)
//
// Private keys:
// d = e^-1 mod ((p-1)(q-1)) // e^-1 = inverse of e
//
// Encrypting:
// cyphertext = m^e mod n
//
// Decrypting:
// message = cyphertext^d mod n
//
// Note: I use a trick involving the chinese remainder theorem to get a 3x speedup in decryption.
// It's well documented on the web so I won't get into detail here.
//
template <int PRECISION>
class RSACrypt
{
public:
typedef Int<PRECISION> Integer;
RSACrypt() {}
~RSACrypt() {}
void Set_Public_Keys(const Integer &pub_n, const Integer &pub_e);
void Set_Keys(const Integer &pub_n, const Integer &pub_e,
const Integer &priv_d, const Integer &keygen_p, const Integer &keygen_q);
void Get_Public_Keys(Integer &pub_n, Integer &pub_e) const;
void Get_Private_Key(Integer &priv_d) const;
void Get_Keygen_Keys(Integer &keygen_p, Integer &keygen_q) const;
bool Load_SSH_Keyset(FileClass *file);
void Encrypt(const Integer &plaintext, Integer &cyphertext) const;
void Decrypt(const Integer &cyphertext, Integer &plaintext) const;
private:
bool Load_Bignum(FileClass *file, Integer &num);
void Decryption_Setup(); // Do precomputation to speedup decryption
Integer PublicN;
Integer PublicE;
Integer PrivateD;
Integer KeygenP; // Primes P & Q generated as part of the keyset
Integer KeygenQ;
// Precomputed values that speed up encryption
Integer DmodPm1; // d mod p-1
Integer DmodQm1; // d mod q-1
Integer RP; // RP = q^(p-1) mod n
Integer RQ; // RQ = p^(q-1) mod n;
};
//
// Set the two public keys: n & e
//
template <int PRECISION>
void RSACrypt<PRECISION>::Set_Public_Keys(const Integer &pub_n, const Integer &pub_e)
{
PublicN=pub_n;
PublicE=pub_e;
}
//
// Set the public & private keys: n, e & d
//
template <int PRECISION>
void RSACrypt<PRECISION>::Set_Keys(const Integer &pub_n, const Integer &pub_e,
const Integer &priv_d, const Integer &keygen_p, const Integer &keygen_q)
{
PublicN=pub_n;
PublicE=pub_e;
PrivateD=priv_d;
KeygenP=keygen_p;
KeygenQ=keygen_q;
Decrtyption_Setup();
}
//
// Get the public keys
//
template <int PRECISION>
void RSACrypt<PRECISION>::Get_Public_Keys(Integer &pub_n, Integer &pub_e) const
{
pub_n=PublicN;
pub_e=PublicE;
}
//
// Get the private key
//
template <int PRECISION>
void RSACrypt<PRECISION>::Get_Private_Key(Integer &priv_d) const
{
priv_d=PrivateD;
}
//
// Get the private numbers created during the keyset generation
// Private as in revealing these will reveal the private key!
//
template <int PRECISION>
void RSACrypt<PRECISION>::Get_Keygen_Keys(Integer &keygen_p, Integer &keygen_q) const
{
keygen_p=KeygenP;
keygen_q=KeygenQ;
}
//
// Load an RSA private keyset from an OpenSSH "identity" file.
//
template <int PRECISION>
bool RSACrypt<PRECISION>::Load_SSH_Keyset(FileClass *file)
{
assert(file);
if ( ! file)
return(false);
bool retval=true;
unsigned char buffer[1024];
if ( ! file->Open())
return(false);
file->Read(buffer, strlen(AUTHFILE_ID_STRING)+1);
buffer[strlen(AUTHFILE_ID_STRING)]=0; // null term
if (strcmp((char *)buffer, AUTHFILE_ID_STRING))
return(false);
unsigned char cypher_type; // keyfile encryption method
file->Read(&cypher_type, 1);
if (cypher_type != 0)
return(false);
file->Read(buffer, 4); // reserved data
file->Read(buffer, 4); // ignored
retval=retval && Load_Bignum(file, PublicN);
retval=retval && Load_Bignum(file, PublicE);
if (!retval)
return(false);
// comment string
int comment_length;
file->Read(&comment_length, 4);
comment_length=ntohl(comment_length);
file->Read(buffer, comment_length);
// post-decrypt check chars
file->Read(buffer, 4);
if ((buffer[0] != buffer[2]) || (buffer[1] != buffer[3]))
return(false);
Integer q_inv_mod_p; // invserse of q mod p (we don't need this)
retval=retval && Load_Bignum(file, PrivateD);
retval=retval && Load_Bignum(file, q_inv_mod_p);
retval=retval && Load_Bignum(file, KeygenP);
retval=retval && Load_Bignum(file, KeygenQ);
if (!retval)
return(false);
// Any remaining bytes are padding
Decryption_Setup();
return(true);
}
//
// Precomputation for fast decryption
//
template <int PRECISION>
void RSACrypt<PRECISION>::Decryption_Setup(void)
{
Integer temp;
// If p < q, swap p & q
if (KeygenP < KeygenQ)
{
temp=KeygenP;
KeygenP=KeygenQ;
KeygenQ=temp;
}
assert(KeygenP > KeygenQ);
// pm1 = p - 1
Integer pm1(KeygenP);
--pm1;
// pm1 = p - 1
Integer qm1(KeygenQ);
--qm1;
// DmodPm1 = d mod (p-1)
Integer::Unsigned_Divide(DmodPm1, temp, PrivateD, pm1);
assert(DmodPm1 < pm1);
// DmodQm1 = d mod (q-1)
Integer::Unsigned_Divide(DmodQm1, temp, PrivateD, qm1);
assert(DmodQm1 < qm1);
RP = KeygenQ.exp_b_mod_c(pm1, PublicN);
RQ = KeygenP.exp_b_mod_c(qm1, PublicN);
}
//
// RSA Encryption c = m^e mod n
//
template <int PRECISION>
void RSACrypt<PRECISION>::Encrypt(const Integer &plaintext, Integer &cyphertext) const
{
Integer m(plaintext);
cyphertext=m.exp_b_mod_c(PublicE, PublicN);
}
//
// RSA Decryption m = c^d mod n
//
template <int PRECISION>
void RSACrypt<PRECISION>::Decrypt(const Integer &cyphertext, Integer &plaintext) const
{
#ifdef SIMPLE_AND_SLOW_RSA
Integer c(cyphertext);
plaintext=c.exp_b_mod_c(PrivateD, PublicN);
#else
Integer temp;
// Get a version of the cyphertext mod q & p
Integer cmp, cmq;
Integer::Unsigned_Divide(cmp, temp, cyphertext, KeygenP);
Integer::Unsigned_Divide(cmq, temp, cyphertext, KeygenQ);
// mp = cmp ^ dmp mod p
Integer mp;
mp=cmp.exp_b_mod_c(DmodPm1, KeygenP);
// mq = cmq ^ dmq mod q
Integer mq;
mq=cmq.exp_b_mod_c(DmodQm1, KeygenQ);
Integer sp, sq;
//sp=mp * RP mod n;
//sq=mq * RQ mod n;
XMP_Prepare_Modulus(&PublicN.reg[0], PRECISION);
XMP_Mod_Mult(&sp.reg[0], &mp.reg[0], &RP.reg[0], PRECISION);
XMP_Mod_Mult(&sq.reg[0], &mq.reg[0], &RQ.reg[0], PRECISION);
XMP_Mod_Mult_Clear(PRECISION);
plaintext = sp+sq;
if (plaintext >= PublicN)
plaintext-=PublicN;
#endif
}
////////////////////////////////// Private Methods Below ///////////////////////////////////
//
// Load a large number from the SSH keyset file
//
template <int PRECISION>
bool RSACrypt<PRECISION>::Load_Bignum(FileClass *file, Integer &num)
{
int readlen;
unsigned char buffer[1024];
unsigned short int n_bits, n_bytes;
readlen=file->Read(&n_bits, 2); // bits in network byte order
if (readlen != 2)
return(false);
n_bits=ntohs(n_bits);
n_bytes=(n_bits+7)/8;
readlen=file->Read(buffer, n_bytes);
if (readlen != n_bytes)
return(false);
num.Unsigned_Decode(buffer, n_bytes);
return(true);
}
#endif