AES實現財務資料的加密解密儲存

abingtech 發表於 2020-06-01
  • 需求背景

眾所周知,金融行業有各種各樣的財務報表,有些報表涉及到公司財務或經營相關的敏感資料,需要進行加密儲存,只有掌握金鑰的使用者才能看到解密後的資料。注意,這裡所說的加密並不是針對整個資料庫或者表全域性加密,而是針對表的某些欄位進行加密。

 

  • 實現思路

1、針對某一張報表建立相對應的一張落地表,相關需要加密的欄位統一定義為VARCHAR2(1000)。

2、實現Hibernate監聽器介面,在實體儲存之前進行加密,資料Load出來之後進行解密,這樣可以實現加密解密邏輯的統一處理。

3、對是否需要加密的實體和欄位標註對應的註解,將加密解密的實現邏輯轉化為對註解的解析處理。

4、統一採用AES加密解密演算法,放在工具類中實現,加密解密流程(原圖摘自:https://blog.csdn.net/gulang03/article/details/81175854)如下:

AES實現財務資料的加密解密儲存

 

 5、其他需要處理的場景

     a、資料的儲存和查詢採用的是JDBC的處理方式,需要在SQL中統一處理(建議用JdbcTemplate底層統一封裝下),mysql有內建函式,oracle需要自定義函式實現

     b、資料修改時也需要考慮加密的場景

     c、oracle正常情況,使用者不能訪問sys.dbms_crypto,需要DBA授權

 

  •  核心程式碼

 1、註冊監聽器事件

public class HibernateEvent {

    @Autowired
    private SessionFactory sessionFactory;
    @Autowired
    private AesListener aesListener;

    @PostConstruct
    public void registerListeners() {
        EventListenerRegistry registry = ((SessionFactoryImpl) sessionFactory).getServiceRegistry().getService(
                EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(aesListener);
        registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(aesListener);
    }

}

2、監聽器實現

@Component
public class AesListener implements PreInsertEventListener, PostLoadEventListener {
    @Override
    public boolean onPreInsert(PreInsertEvent event) {

        Class clazz = event.getPersister().getMappedClass();

        String[] propertyNames = event.getPersister().getPropertyNames();

        EnableEncrypted enableEncrypted = (EnableEncrypted)clazz.getAnnotation(EnableEncrypted.class);

        if(enableEncrypted != null){
            Field[] fields = clazz.getDeclaredFields();
            if(fields != null){
                for(Field field : fields){
                    boolean isAccessible = field.isAccessible();
                    if(!isAccessible){
                        field.setAccessible(true);
                    }
                    Encrypted encrypted = field.getAnnotation(Encrypted.class);
                    if(encrypted != null){
                        try {
                            Object value = field.get(event.getEntity());

                            Class<?> type = field.getType();
                            String enStr = null;
                            if(type == String.class){
                                enStr = AesUtils.aesEncrypt(value.toString());
                            }else if(type == Double.class){
                                Double val = (Double)value;
                                enStr = AesUtils.aesDecrypt(String.valueOf(val));
                            }

                            if(enStr != null){
                                field.set(event.getEntity(), enStr);
                                field.setAccessible(isAccessible);

                                for(int i = 0; i < propertyNames.length; i++){
                                    if(field.getName().equals(propertyNames[i])){
                                        event.getState()[i] = enStr;
                                        break;
                                    }
                                }
                            }
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }
}

3、加密解密工具類

public class AesUtils {

    public static String selectSql(String columnName){
        if(StringUtils.isNotEmpty(columnName)){
            String alias = columnName.contains(".")?columnName.substring(columnName.lastIndexOf(".")+1):columnName;
            return whereSql(columnName) + " AS "+alias;
        }
        return null;
    }

    public static String whereSql(String columnName){
        if(StringUtils.isNotEmpty(columnName)){
            return "CAST(AES_DECRYPT(UNHEX("+columnName+"), '"+ AesConstants.AES_KEY +"') AS CHAR)";
        }
        return null;
    }

    public static String writeSql(String columnValue){
        if(StringUtils.isNotEmpty(columnValue)){
            return "HEX(AES_ENCRYPT('"+columnValue+"', '"+AesConstants.AES_KEY+"'))";
        }
        return null;
    }

    private static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    private static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length()/2];
        for (int i = 0;i< hexStr.length()/2; i++) {
            int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    public static String aesEncrypt(String content) {
        try {
            SecretKey key = generateMySQLAESKey("ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] cleartext = content.getBytes("UTF-8");
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(Hex.encodeHex(ciphertextBytes));

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String aesDecrypt(String content){
        try {
            SecretKey key = generateMySQLAESKey("ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] cleartext = new byte[0];
            try {
                cleartext = Hex.decodeHex(content.toCharArray());
            } catch (DecoderException e) {
                log.error("非法的16進位制字串:" + content, e);
            }
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(ciphertextBytes, "UTF-8");

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static SecretKeySpec generateMySQLAESKey(final String encoding) {
        try {
            final byte[] finalKey = new byte[16];
            int i = 0;
            for(byte b : AesConstants.AES_KEY.getBytes(encoding))
                finalKey[i++%16] ^= b;
            return new SecretKeySpec(finalKey, "AES");
        } catch(UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

}

 

最新文章