Compare commits

..

5 Commits

Author SHA1 Message Date
256d9d5937 add Regex 2025-11-04 17:40:57 +08:00
8f161590c1 not throw NoSuchAlgorithmException in Digest 2025-10-30 17:11:35 +08:00
53985cd358 add argon2id digest 2025-10-30 17:08:50 +08:00
4a633765e8 add AES in Encryptor and Decryptor 2025-10-30 16:47:55 +08:00
9e6b97632a add Time.parseToMS(String) 2025-10-24 14:19:38 +08:00
9 changed files with 366 additions and 48 deletions

View File

@ -6,7 +6,7 @@
<groupId>com.imyeyu.utils</groupId> <groupId>com.imyeyu.utils</groupId>
<artifactId>timi-utils</artifactId> <artifactId>timi-utils</artifactId>
<version>0.0.1</version> <version>0.0.2</version>
<properties> <properties>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
@ -79,6 +79,11 @@
<artifactId>timi-java</artifactId> <artifactId>timi-java</artifactId>
<version>0.0.1</version> <version>0.0.1</version>
</dependency> </dependency>
<dependency>
<groupId>de.mkammerer</groupId>
<artifactId>argon2-jvm</artifactId>
<version>2.12</version>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>

View File

@ -0,0 +1,50 @@
package com.imyeyu.utils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @author 夜雨
* @since 2025-10-29 16:11
*/
public class Decryptor {
/**
* 解密字符串
*
* @param data 待解密字节数据
* @param key 密钥
* @return 解密结果
*/
public byte[] aes(String data, byte[] key) {
return aes(data.getBytes(), key);
}
/**
* 解密
*
* @param data 待解密字节数据
* @param key 密钥
* @return 解密结果
*/
public byte[] aes(byte[] data, byte[] key) {
try {
SecretKey secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
throw new RuntimeException("invalid data");
} catch (InvalidKeyException e) {
throw new RuntimeException("invalid key");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("not support AES encrypt");
}
}
}

View File

@ -1,5 +1,8 @@
package com.imyeyu.utils; package com.imyeyu.utils;
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -12,72 +15,102 @@ import java.security.NoSuchAlgorithmException;
*/ */
public class Digest { public class Digest {
public static String md5(String data) throws NoSuchAlgorithmException { public static String md5(String data) {
return md5(data, StandardCharsets.UTF_8); return md5(data, StandardCharsets.UTF_8);
} }
public static String md5(String data, Charset charset) throws NoSuchAlgorithmException { public static String md5(String data, Charset charset) {
return md5(data.getBytes(charset)); return md5(data.getBytes(charset));
} }
public static String md5(byte[] data) throws NoSuchAlgorithmException { public static String md5(byte[] data) {
if (data == null || data.length == 0) { try {
return null; if (data == null || data.length == 0) {
return null;
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(data);
byte[] bytes = md5.digest();
char[] chars = new char[bytes.length * 2];
for (int i = 0, j = 0; i < bytes.length; i++) {
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] >>> 4 & 0xF];
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] & 0xF];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("unsupported md5 digest");
} }
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(data);
byte[] bytes = md5.digest();
char[] chars = new char[bytes.length * 2];
for (int i = 0, j = 0; i < bytes.length; i++) {
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] >>> 4 & 0xF];
chars[j++] = Text.HEX_DIGITS_LOWER[bytes[i] & 0xF];
}
return new String(chars);
} }
public static String sha1(String data) throws NoSuchAlgorithmException { public static String sha1(String data) {
return sha1(data, StandardCharsets.UTF_8); return sha1(data, StandardCharsets.UTF_8);
} }
public static String sha1(String data, Charset charset) throws NoSuchAlgorithmException { public static String sha1(String data, Charset charset) {
return sha1(data.getBytes(charset)); return sha1(data.getBytes(charset));
} }
public static String sha1(byte[] bytes) throws NoSuchAlgorithmException { public static String sha1(byte[] bytes) {
MessageDigest sha = MessageDigest.getInstance("SHA"); try {
sha.update(bytes); MessageDigest sha = MessageDigest.getInstance("SHA");
return Text.byteToHex(sha.digest()); sha.update(bytes);
return Text.byteToHex(sha.digest());
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("unsupported sha1 digest");
}
} }
public static String sha256(String data) throws NoSuchAlgorithmException { public static String sha256(String data) {
return sha256(data, StandardCharsets.UTF_8); return sha256(data, StandardCharsets.UTF_8);
} }
public static String sha256(String data, Charset charset) throws NoSuchAlgorithmException { public static String sha256(String data, Charset charset) {
return sha256(data.getBytes(charset)); return sha256(data.getBytes(charset));
} }
public static String sha256(byte[] bytes) throws NoSuchAlgorithmException { public static String sha256(byte[] bytes) {
MessageDigest sha = MessageDigest.getInstance("SHA-256"); try {
sha.update(bytes); MessageDigest sha = MessageDigest.getInstance("SHA-256");
return Text.byteToHex(sha.digest()); sha.update(bytes);
return Text.byteToHex(sha.digest());
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("unsupported sha256 digest");
}
} }
public static String sha512(String data) throws NoSuchAlgorithmException { public static String sha512(String data) {
return sha512(data, StandardCharsets.UTF_8); return sha512(data, StandardCharsets.UTF_8);
} }
public static String sha512(String data, Charset charset) throws NoSuchAlgorithmException { public static String sha512(String data, Charset charset) {
return sha512(data.getBytes(charset)); return sha512(data.getBytes(charset));
} }
public static String sha512(byte[] bytes) throws NoSuchAlgorithmException { public static String sha512(byte[] bytes) {
MessageDigest sha = MessageDigest.getInstance("SHA-512"); try {
BigInteger number = new BigInteger(1, sha.digest(bytes)); MessageDigest sha = MessageDigest.getInstance("SHA-512");
StringBuilder result = new StringBuilder(number.toString(16)); BigInteger number = new BigInteger(1, sha.digest(bytes));
while (result.length() < 64) { StringBuilder result = new StringBuilder(number.toString(16));
result.insert(0, "0"); while (result.length() < 64) {
result.insert(0, "0");
}
return result.toString();
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("unsupported sha512 digest");
} }
return result.toString(); }
public static String argon2id(String password) {
return argon2id(10, 65536, 1, password);
}
public static String argon2id(int iterations, int memory, int parallelism, String password) {
Argon2 argon2id = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
return argon2id.hash(iterations, memory, parallelism, password.toCharArray());
}
public static boolean argon2idVerify(String passwordHash, String password) {
Argon2 argon2id = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
return argon2id.verify(passwordHash, password.toCharArray());
} }
} }

View File

@ -0,0 +1,65 @@
package com.imyeyu.utils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @author 夜雨
* @since 2025-10-29 16:02
*/
public class Encryptor {
public byte[] aesKey() {
return aesKey(256);
}
public byte[] aesKey(int size) {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(size);
return keyGen.generateKey().getEncoded();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("not support AES encrypt");
}
}
/**
* 加密字符串
*
* @param data 待加密字符串
* @param key 密钥
* @return 加密结果
*/
public byte[] aes(String data, byte[] key) {
return aes(data.getBytes(), key);
}
/**
* 加密
*
* @param data 待加密字节数据
* @param key 密钥
* @return 加密结果
*/
public byte[] aes(byte[] data, byte[] key) {
try {
SecretKey secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
throw new RuntimeException("invalid data");
} catch (InvalidKeyException e) {
throw new RuntimeException("invalid key");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("not support AES encrypt");
}
}
}

View File

@ -0,0 +1,93 @@
package com.imyeyu.utils;
import com.imyeyu.java.TimiJava;
import java.util.regex.Pattern;
/**
* @author 夜雨
* @since 2025-10-30 17:16
*/
public class Regex {
/** 手机号码 (11 位,以 1 开头) */
public static final String MOBILE_PHONE = "^1[3-9]\\d{9}$";
/** 固定电话号码 (带区号010-12345678 或 0512-1234567) */
public static final String FIXED_PHONE = "^(0\\d{2,3}-)?[1-9]\\d{6,7}$";
/** 身份证号码 (15 位或 18 位) */
public static final String ID_CARD = "(^\\d{15}$)|(^\\d{17}([0-9]|X|x)$)";
/** 邮箱地址 */
public static final String EMAIL = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
/** 中文姓名 (2-10 个中文字符) */
public static final String CHINESE_NAME = "^[\\u4e00-\\u9fa5]{2,10}$";
/** 邮政编码 (6 位数字) */
public static final String POSTAL_CODE = "^[1-9]\\d{5}$";
/** 银行卡号 (16-19 位数字) */
public static final String BANK_CARD = "^[1-9]\\d{15,18}$";
/** QQ号码 (5-12 位数字) */
public static final String QQ_NUMBER = "^[1-9]\\d{4,11}$";
/** 微信号 (6-20 位字母、数字、下划线、减号) */
public static final String WECHAT_ID = "^[a-zA-Z][-_a-zA-Z0-9]{5,19}$";
/** 车牌号码 (包含新能源车牌) */
public static final String LICENSE_PLATE = "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]{1}[A-Z]{1}[A-Z0-9]{4,5}[A-Z0-9挂学警港澳]{1}$";
/** 日期格式 (yyyy-MM-dd) */
public static final String DATE = "^\\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\\d|3[01])$";
/** 时间格式 (HH:mm:ss) */
public static final String TIME = "^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$";
/** 金额 (支持小数,最多两位小数) */
public static final String MONEY = "^[0-9]+(\\.[0-9]{1,2})?$";
/** 用户名 (4-16 位字母、数字、下划线) */
public static final String USERNAME = "^[a-zA-Z0-9_]{4,16}$";
/** 密码 (6-18 位,包含字母和数字) */
public static final String PASSWORD = "^(?=.*[a-zA-Z])(?=.*\\d)[a-zA-Z0-9]{6,18}$";
/** 中文字符 */
public static final String CHINESE_CHAR = "^[\\u4e00-\\u9fa5]+$";
/** 网址 */
public static final String URL = "^(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$";
/** IP 地址 */
public static final String IP_ADDRESS = "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$";
/** 统一社会信用代码 */
public static final String UNIFIED_SOCIAL_CREDIT_CODE = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$";
/** 港澳居民来往内地通行证 */
public static final String HONG_KONG_MACAO_PASS = "^[HMhm]{1}([0-9]{10}|[0-9]{8})$";
/** 台湾居民来往大陆通行证 */
public static final String TAIWAN_PASS = "^[0-9]{8}|[0-9]{10}$";
/** 护照 */
public static final String PASSPORT = "^[a-zA-Z0-9]{5,17}$";
public static boolean isMatch(String regex, String text) {
if (TimiJava.isEmpty(regex) || TimiJava.isEmpty(text)) {
return false;
}
try {
return Pattern.matches(regex, text);
} catch (Exception e) {
return false;
}
}
public static boolean isNotMatch(String regex, String text) {
return !isMatch(regex, text);
}
}

View File

@ -126,17 +126,6 @@ public class Text {
return String.format("%0" + l + "d", number); return String.format("%0" + l + "d", number);
} }
/**
* 正则表达式测试
*
* @param reg 正则
* @param value 文本
* @return true 为匹配
*/
public static boolean testReg(String reg, String value) {
return Pattern.compile(reg).matcher(value).matches();
}
/** /**
* 驼峰转下划线 * 驼峰转下划线
* *

View File

@ -255,6 +255,51 @@ public class Time {
return Instant.ofEpochMilli(unixTime).atZone(ZoneId.systemDefault()).toLocalDateTime(); return Instant.ofEpochMilli(unixTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
} }
/**
* 将时间字符串解析为毫秒值
*
* <p>支持的格式示例:
* <pre>
* 10 = 10
* 10ms = 10
* 10s = 10,000
* 10m = 600,000
* 10h = 36,000,000
* 10d = 10D = 10 d = 864,000,000
* 10.5d = 907,200,000
* </pre>
*
* @param timeStr 时间字符串
* @return 毫秒
* @throws IllegalArgumentException 输入格式无效
*/
public static long parseToMS(String timeStr) {
if (TimiJava.isEmpty(timeStr)) {
throw new IllegalArgumentException("not found timeStr");
}
String normalized = timeStr.replaceAll("\\s+", "").toLowerCase();
Pattern pattern = Pattern.compile("^(\\d+(?:\\.\\d+)?)(ms|[dhms])?$");
Matcher matcher = pattern.matcher(normalized);
if (!matcher.matches()) {
throw new IllegalArgumentException("invalid format: " + timeStr);
}
double value = Double.parseDouble(matcher.group(1));
String unit = matcher.group(2);
if (TimiJava.isEmpty(unit) || unit.equals("ms")) {
return Math.round(value);
}
double multiplier = switch (unit) {
case "s" -> Time.S;
case "m" -> Time.M;
case "h" -> Time.H;
case "d" -> Time.D;
default -> throw new IllegalArgumentException("invalid format unit: " + unit);
};
return Math.round(value * multiplier);
}
/** /**
* 媒体时间操作 * 媒体时间操作
* *

View File

@ -0,0 +1,22 @@
package test;
import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Time;
import org.junit.jupiter.api.Test;
/**
* @author 夜雨
* @since 2025-10-30 16:55
*/
public class TestDigest {
@Test
public void testArgon2id() {
String pwd = "hello argon";
long start = Time.now();
String hash = Digest.argon2id(pwd);
System.out.println("digest time: " + (Time.now() - start) + "ms");
System.out.println(hash);
assert Digest.argon2idVerify(hash, pwd);
}
}

View File

@ -1,8 +1,24 @@
package test; package test;
import com.imyeyu.utils.Time;
import org.junit.jupiter.api.Test;
/** /**
* @author 夜雨 * @author 夜雨
* @since 2024-12-19 22:10 * @since 2024-12-19 22:10
*/ */
public class TestTime { public class TestTime {
@Test
public void testParseToMS() {
assert Time.parseToMS("10") == 10;
assert Time.parseToMS("10ms") == 10;
assert Time.parseToMS("10MS") == 10;
assert Time.parseToMS("10 ms") == 10;
assert Time.parseToMS("10s") == Time.S * 10;
assert Time.parseToMS("10m") == Time.M * 10;
assert Time.parseToMS("10h") == Time.H * 10;
assert Time.parseToMS("10d") == Time.D * 10;
assert Time.parseToMS("10.5d") == Time.D * 10 + Time.D * .5;
}
} }