Spring aop+反射實現電話號加密

加密算法

為什麼需要加密呢?就好比戰爭時期特工在進行傳輸情報的時候,如果將情報明文直接通過某種媒介傳輸給同盟人員,那麼一旦情報被地方截取,就會釀成大禍。如果將明文通過某種加密算法加密成雜亂無章的密文,即使被敵方截獲,沒有對應的解密算法,也很難識別出其中的明文。安全傳輸領域,加密算法是一種很常用的手段,它可以保證數據不被竊取和泄漏,還可以保證數據的完整性,不被篡改。

常見的加密算法有對稱加密,非對稱加密,單向加密(簽名)等分類。其中對稱加密算法,加密密鑰和解密密鑰是同一個,因此發送發和接收方都需要維護一個相同的密鑰,如果密鑰要修改,雙方都需要同時修改。非對稱加密算法中,發送發用公鑰進行加密,接收方用私鑰進行解密。單向加密算法是對傳輸的數據生成一個簽名,通過這個簽名來驗證數據在傳輸過程中是否被篡改過,一般是不可逆的。

AES

  • AES 加密算法是一種對稱加密算,加密密鑰和解密密鑰是同一個。它采用對稱分組密碼體制,最少支持長度為128位的加密。涉及到分組加密,padding填充,初始向量IV,密鑰,四種加密模式。
  • 分組加密就是將原文分割成一段段的分別進行加密,每段分組長度為128位16個字節,如果最後一組長度不足128位,則采用padding填充模式將其補齊到128位。然後對每組進行加密,最後組成最終密文。
  • padding填充是為瞭解決分組後的長度不足128位的場景。填充模式也有多種不同模式,比如PKCS5, PKCS7和NOPADDING。其中PKSC5是指分組後缺少幾個字節,就在後面填充幾個字節的幾,比如缺少2個字節,就在後面填充2個字節的2。PKCS7是指缺少幾個字節,就在後面填充幾個字節的0,比如缺少5個字節,就填充5個字節的0。NOPADDING模式就是不需要填充。如果最後面剛好是16個字節的16,那麼解密方不知道是填充數據還是真實數據,因此會在後面再補16個字節的16來區分。
  • 初始向量IV是為瞭保證數據的安全性,如果我們對同一段內容進行加密後,所生成的密文應該是相同的,那麼這樣就很容易通過密文分析出哪些段是相同的。比如原文分組後成為ABCADE,加密後的密文是GHIGJK,那麼很容易看出那兩段內容是相同的。第一個分組在初始加密向量的基礎上進行加密,以後的每一個分組都在前一個分組加密的結果為基礎進行加密,從而保證瞭即使相同的原文段,也不會生成相同的密文段。
  • 密鑰是加密和解密公用的一個,它一般是128位16個字節長度的隨機字符串,分組後的原文都用同一個密鑰進行加密。
  • 加密模式包含ECB,CBC, CFB, OFB等四種模式。ECB分別對每個分組進行加密,相同的明文會被加密成相同的密文。CBC模式會使用上一段的加密結果作為加密向量,相同的原文不會被加密成相同的密文。

MD5

MD5算法是一種不可逆的簽名算法,對相同的輸入通過MD5散列函數處理後,會輸出相同的信息。因此MD5可以驗證傳輸的數據是否有被篡改,但是如果竊密者對明文進行瞭修改後,再使用MD5算法進行散列,接收方將無法判斷明文已經被修改瞭。一般數據庫存儲用戶密碼會將密碼使用MD5進行處理。

HMAC-MD5

HMAC-MD5由一個H函數和一個密鑰組成,一般我們采用的散列函數為Md5或者SHA-1。HMAC-MD5算法就是采用密鑰加密+Md5信息摘要的方式形成新的密文。

AOP

眾所周知,AOP(面向切面編程)是Spring一個重要特性,它將核心關註點和業務邏輯進行解耦,將業務無關的邏輯提取出來作為公共模塊進行處理。它有切點,切面,連接點,通知的概念。切點就是我們可以織入切面的點,切面就是我們要織入的橫切邏輯,通知包含前置通知,後置通知,返回通知,異常通知,環繞通知等。

1. 引入依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.18</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

2. 業主信息類、業主信息服務類

@Data
@AllArgsConstructor
public class CustomerInfo {

    private String name;

    private String phoneNum;
}

@Service
public class CustomerInfoService {

    @PhoneEncryptionForList(fields = "phoneNum", clazz = CustomerInfo.class)
    public List<CustomerInfo> listCustomerInfo() {
        return Arrays.asList(
                new CustomerInfo("小王", "15112368569"),
                new CustomerInfo("小李", "13652298565"),
                new CustomerInfo("小武", "18965653698"),
                new CustomerInfo("小天", "13192558569")
        );
    }
}

3. 電話號加密註解,電話號加密切面類

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PhoneEncryptionForList {

    /**
     * 加密字段
     *
     * @return
     */
    String[] fields();

    /**
     * 加密對象類型
     *
     * @return
     */
    Class<?> clazz();
}

@Aspect
@Component
public class PhoneEncryptionForListAspect {

    private static final int PHONE_SIZE = 11;

    @AfterReturning(value = "@annotation(com.zzq.spring.phone.encryption.PhoneEncryptionForList)", returning = "result")
    public void doEncrypt(JoinPoint joinPoint, Object result) {
        if (result == null || !(result instanceof List)) {
            System.out.println("result class type is not list, annotation is invalid");
            return;
        }

        List list = (List) result;
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        // 獲取註解的屬性值
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        PhoneEncryptionForList annotation = method.getAnnotation(PhoneEncryptionForList.class);
        String[] fields = annotation.fields();
        Class<?> clazz = annotation.clazz();

        for (String field : fields) {
            try {
                // 反射獲取實體類加密字段相應的 set 和 get 方法
                char[] chars = field.toCharArray();
                chars[0] = (char) (chars[0] - 32);
                String setMethodName = "set" + new String(chars);
                String getMethodName = "get" + new String(chars);
                Method getMethod = clazz.getDeclaredMethod(getMethodName);
                Method setMethod = clazz.getDeclaredMethod(setMethodName, String.class);

                for (Object obj : list) {
                    doEncryptPhone(getMethod, setMethod, obj);
                }
            } catch (Exception e) {
                System.out.println(field + " encrypt exception, " + e.getMessage());
                continue;
            }
        }
    }

    private static void doEncryptPhone(Method getMethod, Method setMethod, Object obj) throws Exception {
        // 反射調用 get 方法
        String originalPhone = (String)getMethod.invoke(obj);
        if (!StringUtils.hasText(originalPhone) || originalPhone.length() != PHONE_SIZE) {
            System.out.println("phone field value is blank or formal error");
            return;
        }

        String encryptedPhone = originalPhone.replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");
        // 反射調用 set 方法
        setMethod.invoke(obj, encryptedPhone);
    }
}

4. 測試類

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.zzq.spring.phone.encryption")
public class TestApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TestApplication.class);

        CustomerInfoService customerInfoService = ctx.getBean(CustomerInfoService.class);
        List<CustomerInfo> afterPhoneEncryptCustomerInfos = customerInfoService.listCustomerInfo();

        afterPhoneEncryptCustomerInfos.forEach(customerInfos -> {
            System.out.println(customerInfos);
        });
    }
}

5. 結果和總結

該加密註解隻簡單實現瞭電話號加密,註解中還可以新增一些屬性擴展註解功能,比如增加加密字段格式加密規則的正則表達式的屬性值,這樣加密的字段類型(手機號、身份證等)和方式(中間多少位加密、首位加密)就可以根據具體需求處理。

以上就是Spring aop+反射實現電話號加密的詳細內容,更多關於Spring aop電話號加密的資料請關註WalkonNet其它相關文章!

推薦閱讀: