/**
 * @Author:        Kane Wang <wangkane@qq.com>
 * @Date:          2025-03-15 12:50:53
 * @LastEditors:   Kane Wang
 * @LastModified:  2025-03-16 08:43:56
 * @FilePath:      src/main/java/com/cpic/xim/utils/secrecy/RSAUtils.java
 * @Description:
 * 
 * Copyright (c) 2025 by Kane All rights reserved
 */
package com.cpic.xim.utils.secrecy;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSAUtils
 */
public class RSAUtils
{

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 生成公钥和私钥
     *
     * @throws Exception
     */
    public static Map<String, String> getKeys()
                                                throws Exception
    {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance( "RSA" );
        keyPairGen.initialize( 1024 );
        KeyPair       keyPair    = keyPairGen.generateKeyPair();
        RSAPublicKey  publicKey  = ( RSAPublicKey ) keyPair.getPublic();
        RSAPrivateKey privateKey = ( RSAPrivateKey ) keyPair.getPrivate();

        String publicKeyStr  = getPublicKeyStr( publicKey );
        String privateKeyStr = getPrivateKeyStr( privateKey );

        Map<String, String> result = new HashMap<>( 2 );
        result.put( "publicKeyStr", publicKeyStr );
        result.put( "privateKeyStr", privateKeyStr );

        return result;
    }

    public static void main( String[] args )
                                             throws Exception
    {
        Map<String, String> keys = getKeys();
        System.out.println( "publicKeyStr=" + keys.get( "publicKeyStr" ) );
        System.out.println( "privateKeyStr=" + keys.get( "privateKeyStr" ) );
    }

    /**
     * 使用模和指数生成RSA私钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA
     * /None/NoPadding】
     *
     * @param modulus  模
     * @param exponent 指数
     * @return
     */
    public static RSAPrivateKey getPrivateKey( String modulus, String exponent )
    {
        try
        {
            BigInteger        b1         = new BigInteger( modulus );
            BigInteger        b2         = new BigInteger( exponent );
            KeyFactory        keyFactory = KeyFactory.getInstance( "RSA" );
            RSAPrivateKeySpec keySpec    = new RSAPrivateKeySpec( b1, b2 );
            return ( RSAPrivateKey ) keyFactory.generatePrivate( keySpec );
        }
        catch ( Exception e )
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 公钥加密
     *
     * @param data      加密数据
     * @param publicKey 加密key
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey( String data, String publicKey )
                                                                             throws Exception
    {
        byte[]             dataByte    = data.getBytes();
        byte[]             keyBytes    = Base64Utils.decode( publicKey );
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( keyBytes );
        KeyFactory         keyFactory  = KeyFactory.getInstance( KEY_ALGORITHM );
        Key                publicK     = keyFactory.generatePublic( x509KeySpec );
        // 对数据加密
        Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" );
        cipher.init( Cipher.ENCRYPT_MODE, publicK );
        int                   inputLen = dataByte.length;
        ByteArrayOutputStream out      = new ByteArrayOutputStream();
        int                   offSet   = 0;
        byte[]                cache;
        int                   i        = 0;
        // 对数据分段加密
        while ( inputLen - offSet > 0 )
        {
            if ( inputLen - offSet > MAX_ENCRYPT_BLOCK )
            {
                cache = cipher.doFinal( dataByte, offSet, MAX_ENCRYPT_BLOCK );
            }
            else
            {
                cache = cipher.doFinal( dataByte, offSet, inputLen - offSet );
            }
            out.write( cache, 0, cache.length );
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return Base64Utils.encode( encryptedData );
    }

    /**
     * 私钥解密
     *
     * @param privateKey 私钥
     * @param data       加密数据
     * @return
     * @throws Exception
     */
    public static String decryptByPrivateKey( String privateKey, String data )
                                                                               throws Exception
    {
        byte[]              encryptedData = Base64Utils.decode( data );
        byte[]              keyBytes      = Base64Utils.decode( privateKey );
        PKCS8EncodedKeySpec pkcs8KeySpec  = new PKCS8EncodedKeySpec( keyBytes );
        KeyFactory          keyFactory    = KeyFactory.getInstance( KEY_ALGORITHM );
        Key                 privateK      = keyFactory.generatePrivate( pkcs8KeySpec );
        // Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        Cipher cipher = Cipher.getInstance( "RSA/ECB/PKCS1Padding" );

        cipher.init( Cipher.DECRYPT_MODE, privateK );
        int                   inputLen = encryptedData.length;
        ByteArrayOutputStream out      = new ByteArrayOutputStream();
        int                   offSet   = 0;
        byte[]                cache;
        int                   i        = 0;
        // 对数据分段解密
        while ( inputLen - offSet > 0 )
        {
            if ( inputLen - offSet > MAX_DECRYPT_BLOCK )
            {
                cache = cipher.doFinal( encryptedData, offSet, MAX_DECRYPT_BLOCK );
            }
            else
            {
                cache = cipher.doFinal( encryptedData, offSet, inputLen - offSet );
            }
            out.write( cache, 0, cache.length );
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return new String( decryptedData );
    }

    public static String getPrivateKeyStr( PrivateKey privateKey )
    {
        return Base64Utils.encode( privateKey.getEncoded() );
    }

    public static String getPublicKeyStr( PublicKey publicKey )
    {
        return Base64Utils.encode( publicKey.getEncoded() );
    }
}