Java AES加解密工具类封装

本文将介绍如何使用JDK中的Java密码体系结构(JCA)来实现AES加密和解密。对称密钥块密码在数据加密中起重要作用。这意味着同一密钥可用于加密和解密。高级加密标准(AES)是一种广泛使用的对称密钥加密算法。

一、AES算法是什么?

AES算法是一种迭代的对称密钥块密码,它支持128、192和256位的加密密钥(秘密密钥),以对128位的块中的数据进行加密和解密。

二、AES生成密钥的两种方法

在AES中生成密钥的方法有两种:

  • 从随机数生成
  • 从给定密码生成

1、从随机数生成

在第一种方法中,应该从像SecureRandom类这样的加密安全(伪)随机数生成器生成秘密密钥。为了生成密钥,我们可以使用KeyGenerator类。让我们定义一种用于生成大小为n(128、192和256)位的AES密钥的方法:

public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    keyGenerator.init(n);
    SecretKey key = keyGenerator.generateKey();
    return key;
}

2、从给定密码生成

在第二种方法中,可以使用基于密码的密钥派生功能(例如PBKDF2)从给定的密码派生AES秘密密钥。下面方法可通过65,536次迭代和256位密钥长度从给定密码生成AES密钥:

public static SecretKey getKeyFromPassword(String password, String salt)
      throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    return secret;
}

三、AES主要用途

1、加密字符串

1)初始化向量IV

要实现输入字符串加密,我们首先需要根据上一节生成密钥和初始化向量IV:

IV是伪随机值,其大小与加密的块相同。我们可以使用SecureRandom类生成随机IV。

让我们定义一种生成IV的方法:

public static IvParameterSpec generateIv() {
    byte[] iv = new byte[16];
    new SecureRandom().nextBytes(iv);
    return new IvParameterSpec(iv);
}

2)对输入字符串进行加密

下一步,我们使用getInstance()方法从Cipher类创建一个实例。此外,我们使用带有秘密密钥,IV和加密模式的init()方法配置密码实例。最后,我们通过调用doFinal()方法对输入字符串进行加密。此方法获取输入字节并以字节为单位返回密文:

public static String encrypt(String algorithm, String input, SecretKey key, IvParameterSpec iv)
      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, 
      InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    byte[] cipherText = cipher.doFinal(input.getBytes());
    return Base64.getEncoder().encodeToString(cipherText);
}

3)使用初始化密码解密内容

为了解密输入字符串,我们可以使用DECRYPT_MODE初始化密码来解密内容:

public static String decrypt(String algorithm, String cipherText, SecretKey key, IvParameterSpec iv) 
      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, 
      InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
    return new String(plainText);
}

4)测试用例

编写一个用于加密和解密字符串输入的测试方法:

@Test
void givenString_whenEncrypt_thenSuccess()
      throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
      BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException { 
    String input = "test";
    SecretKey key = AESUtil.generateKey(128);
    IvParameterSpec ivParameterSpec = AESUtil.generateIv();
    String algorithm = "AES/CBC/PKCS5Padding";
    String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec);
    String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec);
    Assertions.assertEquals(input, plainText);
}

2、加密文件

现在,让我们使用AES算法加密文件。步骤是相同的​​,但是我们需要一些IO类来处理文件。

1)加密文本文件

让我们加密一个文本文件:

public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
      File inputFile, File outputFile) throws IOException, NoSuchPaddingException,
      NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
      BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    FileInputStream inputStream = new FileInputStream(inputFile);
    FileOutputStream outputStream = new FileOutputStream(outputFile);
    byte[] buffer = new byte[64];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        byte[] output = cipher.update(buffer, 0, bytesRead);
        if (output != null) {
            outputStream.write(output);
        }
    }
    byte[] outputBytes = cipher.doFinal();
    if (outputBytes != null) {
        outputStream.write(outputBytes);
    }
    inputStream.close();
    outputStream.close();
}
请注意,不建议尝试将整个文件(尤其是大文件)读入内存。相反,我们一次加密一个缓冲区。

2)解密加密文件

为了解密文件,我们使用类似的步骤,并使用DECRYPT_MODE初始化密码,如前所述。

public static void decryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
      File encryptedFile, File decryptedFile) throws IOException, NoSuchPaddingException,
      NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
      BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    FileInputStream inputStream = new FileInputStream(encryptedFile);
    FileOutputStream outputStream = new FileOutputStream(decryptedFile);
    byte[] buffer = new byte[64];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        byte[] output = cipher.update(buffer, 0, bytesRead);
        if (output != null) {
            outputStream.write(output);
        }
    }
    byte[] output = cipher.doFinal();
    if (output != null) {
        outputStream.write(output);
    }
    inputStream.close();
    outputStream.close();
}

3)测试用例

再次,让我们定义一个用于加密和解密文本文件的测试方法。在这种方法中,我们从测试资源目录中读取test.txt文件,将其加密为一个名为test.encrypted的文件,然后将该文件解密为一个新文件:

@Test
void givenFile_whenEncrypt_thenSuccess() 
      throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException, 
      BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
    SecretKey key = AESUtil.generateKey(128);
    String algorithm = "AES/CBC/PKCS5Padding";
    IvParameterSpec ivParameterSpec = AESUtil.generateIv();
    Resource resource = new ClassPathResource("inputFile/test.txt");
    File inputFile = resource.getFile();
    File encryptedFile = new File("classpath:test.encrypted");
    File decryptedFile = new File("document.decrypted");
    AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile);
    AESUtil.decryptFile(algorithm, key, ivParameterSpec, encryptedFile, decryptedFile);
    assertThat(inputFile).hasSameTextualContentAs(decryptedFile);
}

3、基于密码加解密

我们可以使用从给定密码派生的密钥进行AES加密和解密。

1)生成密钥

为了生成密钥,我们使用getKeyFromPassword()方法。加密和解密步骤与字符串输入部分中显示的步骤相同。

public static SecretKey getKeyFromPassword(String password, String salt)
      throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    return secret;
}

2)密码加密

然后,我们可以使用实例化的密码和提供的密钥来执行加密:

public static String encryptPasswordBased(String plainText, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes()));
    }

3)密码解密

然后,我们可以使用实例化的密码和提供的密钥来执行解密:

public static String decryptPasswordBased(String cipherText, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }

4)测试用例

@Test
void givenPassword_whenEncrypt_thenSuccess() 
      throws InvalidKeySpecException, NoSuchAlgorithmException, 
      IllegalBlockSizeException, InvalidKeyException, BadPaddingException, 
      InvalidAlgorithmParameterException, NoSuchPaddingException {
    String plainText = "blog.ntan520.com";
    String password = "test";
    String salt = "12345678";
    IvParameterSpec ivParameterSpec = AESUtil.generateIv();
    SecretKey key = AESUtil.getKeyFromPassword(password, salt);
    String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec);
    String decryptedCipherText = AESUtil.decryptPasswordBased(cipherText, key, ivParameterSpec);
    Assertions.assertEquals(plainText, decryptedCipherText);
}

4、加密对象

为了加密Java对象,我们需要使用SealedObject类。该对象应可序列化。

1)定义学生类Student

让我们从定义学生类开始:

public class Student implements Serializable {
    private String name;
    private int age;

    // standard setters and getters
}

2)加密Student对象

接下来,让我们加密Student对象:

public static SealedObject encryptObject(String algorithm, Serializable object,
      SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
      NoSuchAlgorithmException, InvalidAlgorithmParameterException, 
      InvalidKeyException, IOException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    SealedObject sealedObject = new SealedObject(object, cipher);
    return sealedObject;
}

3)解密加密的对象

稍后可以使用正确的密码解密加密的对象:

public static Serializable decryptObject(String algorithm, SealedObject sealedObject,
      SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
      NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
      ClassNotFoundException, BadPaddingException, IllegalBlockSizeException, IOException {
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    Serializable unsealObject = (Serializable) sealedObject.getObject(cipher);
    return unsealObject;
}

4)测试用例

让我们写一个测试用例:

@Test
void givenObject_whenEncrypt_thenSuccess() 
      throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
      InvalidAlgorithmParameterException, NoSuchPaddingException, IOException, 
      BadPaddingException, ClassNotFoundException {
    Student student = new Student("test", 20);
    SecretKey key = AESUtil.generateKey(128);
    IvParameterSpec ivParameterSpec = AESUtil.generateIv();
    String algorithm = "AES/CBC/PKCS5Padding";
    SealedObject sealedObject = AESUtil.encryptObject(algorithm, student, key, ivParameterSpec);
    Student object = (Student) AESUtil.decryptObject(algorithm, sealedObject, key, ivParameterSpec);
    assertThat(student).isEqualToComparingFieldByField(object);
}

四、完整源代码

1、AESUtil.java

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.BadPaddingException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SealedObject;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

public class AESUtil {

    public static String encrypt(String algorithm, String input, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] cipherText = cipher.doFinal(input.getBytes());
        return Base64.getEncoder().encodeToString(cipherText);
    }

    public static String decrypt(String algorithm, String cipherText, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
        return new String(plainText);
    }

    public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(n);
        SecretKey key = keyGenerator.generateKey();
        return key;
    }

    public static SecretKey getKeyFromPassword(String password, String salt)
          throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
        return secret;
    }

    public static IvParameterSpec generateIv() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        return new IvParameterSpec(iv);
    }

    public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
          File inputFile, File outputFile) throws IOException, NoSuchPaddingException,
          NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
          BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        FileInputStream inputStream = new FileInputStream(inputFile);
        FileOutputStream outputStream = new FileOutputStream(outputFile);
        byte[] buffer = new byte[64];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            byte[] output = cipher.update(buffer, 0, bytesRead);
            if (output != null) {
                outputStream.write(output);
            }
        }
        byte[] outputBytes = cipher.doFinal();
        if (outputBytes != null) {
            outputStream.write(outputBytes);
        }
        inputStream.close();
        outputStream.close();
    }

    public static void decryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
          File encryptedFile, File decryptedFile) throws IOException, NoSuchPaddingException,
          NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
          BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        FileInputStream inputStream = new FileInputStream(encryptedFile);
        FileOutputStream outputStream = new FileOutputStream(decryptedFile);
        byte[] buffer = new byte[64];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            byte[] output = cipher.update(buffer, 0, bytesRead);
            if (output != null) {
                outputStream.write(output);
            }
        }
        byte[] output = cipher.doFinal();
        if (output != null) {
            outputStream.write(output);
        }
        inputStream.close();
        outputStream.close();
    }

    public static SealedObject encryptObject(String algorithm, Serializable object, SecretKey key,
          IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
          InvalidAlgorithmParameterException, InvalidKeyException, IOException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        SealedObject sealedObject = new SealedObject(object, cipher);
        return sealedObject;
    }

    public static Serializable decryptObject(String algorithm, SealedObject sealedObject, SecretKey key,
          IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
          InvalidAlgorithmParameterException, InvalidKeyException, ClassNotFoundException,
          BadPaddingException, IllegalBlockSizeException, IOException {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        Serializable unsealObject = (Serializable) sealedObject.getObject(cipher);
        return unsealObject;
    }

    public static String encryptPasswordBased(String plainText, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes()));
    }

    public static String decryptPasswordBased(String cipherText, SecretKey key, IvParameterSpec iv)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
          InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

2、AESUtilTest.java

import org.assertj.core.api.WithAssertions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

class AESUtilTest implements WithAssertions {

    @Test
    void givenString_whenEncrypt_thenSuccess()
          throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
          BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
        // given
        String input = "test";
        SecretKey key = AESUtil.generateKey(128);
        IvParameterSpec ivParameterSpec = AESUtil.generateIv();
        String algorithm = "AES/CBC/PKCS5Padding";

        // when
        String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec);
        String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec);

        // then
        Assertions.assertEquals(input, plainText);
    }

    @Test
    void givenFile_whenEncrypt_thenSuccess()
          throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException,
          BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
        // given
        SecretKey key = AESUtil.generateKey(128);
        String algorithm = "AES/CBC/PKCS5Padding";
        IvParameterSpec ivParameterSpec = AESUtil.generateIv();
        File inputFile = Paths.get("src/test/resources/test.txt")
            .toFile();
        File encryptedFile = new File("classpath:test.encrypted");
        File decryptedFile = new File("document.decrypted");

        // when
        AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile);
        AESUtil.decryptFile(algorithm, key, ivParameterSpec, encryptedFile, decryptedFile);

        // then
        assertThat(inputFile).hasSameTextualContentAs(decryptedFile);
        encryptedFile.delete();
        decryptedFile.delete();
    }

    @Test
    void givenObject_whenEncrypt_thenSuccess()
          throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
          InvalidAlgorithmParameterException, NoSuchPaddingException, IOException, BadPaddingException,
          ClassNotFoundException {
        // given
        Student student = new Student("test", 20);
        SecretKey key = AESUtil.generateKey(128);
        IvParameterSpec ivParameterSpec = AESUtil.generateIv();
        String algorithm = "AES/CBC/PKCS5Padding";

        // when
        SealedObject sealedObject = AESUtil.encryptObject(algorithm, student, key, ivParameterSpec);
        Student object = (Student) AESUtil.decryptObject(algorithm, sealedObject, key, ivParameterSpec);

        // then
        assertThat(student).isEqualTo(object);
    }

    @Test
    void givenPassword_whenEncrypt_thenSuccess()
          throws InvalidKeySpecException, NoSuchAlgorithmException, IllegalBlockSizeException,
          InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
        // given
        String plainText = "blog.ntan520.com";
        String password = "test";
        String salt = "12345678";
        IvParameterSpec ivParameterSpec = AESUtil.generateIv();
        SecretKey key = AESUtil.getKeyFromPassword(password, salt);

        // when
        String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec);
        String decryptedCipherText = AESUtil.decryptPasswordBased(cipherText, key, ivParameterSpec);

        // then
        Assertions.assertEquals(plainText, decryptedCipherText);
    }
}

3、Student.java

import java.io.Serializable;
import java.util.Objects;

public class Student implements Serializable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }
}

发表评论