- 需求背景
眾所周知,金融行業有各種各樣的財務報表,有些報表涉及到公司財務或經營相關的敏感資料,需要進行加密儲存,只有掌握金鑰的使用者才能看到解密後的資料。注意,這裡所說的加密並不是針對整個資料庫或者表全域性加密,而是針對表的某些欄位進行加密。
- 實現思路
1、針對某一張報表建立相對應的一張落地表,相關需要加密的欄位統一定義為VARCHAR2(1000)。
2、實現Hibernate監聽器介面,在實體儲存之前進行加密,資料Load出來之後進行解密,這樣可以實現加密解密邏輯的統一處理。
3、對是否需要加密的實體和欄位標註對應的註解,將加密解密的實現邏輯轉化為對註解的解析處理。
4、統一採用AES加密解密演算法,放在工具類中實現,加密解密流程(原圖摘自:https://blog.csdn.net/gulang03/article/details/81175854)如下:
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); } } }