diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3b998..4ae3ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ +## 0.8.0 + +### 调整 + +> 1,移除**PasswordUtil**类,新增**EncryptConfig**配置; +> 2,默认启用网络请求的加解密,前端请配合接口使用。 + ## 0.7.4 ### 新增 + > 现在操作日志默认收集(不包括GET请求):使用方法名称和接口作为参数存储。如果存在OperationLog注解,则使用注解的内容。 ## 0.7.3 diff --git a/pom.xml b/pom.xml index e1dcf6e..74ce33d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 pom springboot-parent 杭州铁晟科技有限公司基础依赖 @@ -57,55 +57,55 @@ com.tiesheng.springboot-parent springboot-database - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-login - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-web - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-util - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-platform - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-message - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-encrypt - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-annotation - 0.7.4 + 0.8.0 com.tiesheng.springboot-parent springboot-poi - 0.7.4 + 0.8.0 diff --git a/springboot-ademo/pom.xml b/springboot-ademo/pom.xml index dc12a20..511342d 100644 --- a/springboot-ademo/pom.xml +++ b/springboot-ademo/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-ademo diff --git a/springboot-ademo/src/main/java/com/tiesheng/demo/DemoApplication.java b/springboot-ademo/src/main/java/com/tiesheng/demo/DemoApplication.java index bc3cea1..1efeb5d 100644 --- a/springboot-ademo/src/main/java/com/tiesheng/demo/DemoApplication.java +++ b/springboot-ademo/src/main/java/com/tiesheng/demo/DemoApplication.java @@ -3,6 +3,7 @@ package com.tiesheng.demo; import com.tiesheng.core.EnableTieshengWeb; import com.tiesheng.demo.config.DemoWebConfigurer; +import com.tiesheng.encrypt.EnableEncryptConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement @SpringBootApplication @EnableTieshengWeb(webConfigurer = DemoWebConfigurer.class) +@EnableEncryptConfig public class DemoApplication { public static void main(String[] args) { diff --git a/springboot-ademo/src/main/java/com/tiesheng/demo/controller/TestController.java b/springboot-ademo/src/main/java/com/tiesheng/demo/controller/TestController.java index 7caecaf..05d00e9 100644 --- a/springboot-ademo/src/main/java/com/tiesheng/demo/controller/TestController.java +++ b/springboot-ademo/src/main/java/com/tiesheng/demo/controller/TestController.java @@ -9,6 +9,7 @@ import com.tiesheng.login.config.token.TsTokenConfig; import com.tiesheng.login.config.token.bean.TokenBean; import com.tiesheng.message.config.aliyun.AliyunSmsConfig; import com.tiesheng.message.pojos.MessageReqResp; +import com.tiesheng.util.config.EncryptConfig; import com.tiesheng.util.config.GlobalConfig; import com.tiesheng.util.config.Ip2regionConfig; import com.tiesheng.util.pojos.ApiResp; @@ -35,6 +36,8 @@ public class TestController { TsTokenConfig tsTokenConfig; @Autowired Ip2regionConfig ip2regionConfig; + @Autowired + EncryptConfig encryptConfig; @RequestMapping("/index") @@ -48,6 +51,7 @@ public class TestController { } @RequestMapping("/redirect") + @TokenIgnore public void redirect(HttpServletResponse response) { // tsTokenConfig.validToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzYwMDY4NzUsImlkIjoiMSIsImVudmlyb25tZW50VHlwZSI6Im1vYmlsZSIsInNlcnZpY2UiOiJjb250ZXN0LXJlc2VydmUiLCJleHRyYSI6IiJ9.nsfxEFpCNHC7eNCS5DJXdu1VDdnHrTjSfgrozND70Lc", true); // globalConfig.redirect("mobile", "/test", response); @@ -102,4 +106,16 @@ public class TestController { return ApiResp.respOK(CollUtil.newArrayList(file, file1)); } + + @RequestMapping("passwd") + @TokenIgnore + public ApiResp passwd() { + + String passwdCreate = encryptConfig.passwdCreate("12345Zeng!", ""); + LogFactory.get().info(passwdCreate); + encryptConfig.passwdVerify("12345Zeng!", passwdCreate); + + return ApiResp.respOK(""); + } + } diff --git a/springboot-annotation/pom.xml b/springboot-annotation/pom.xml index 0b201fa..970678b 100644 --- a/springboot-annotation/pom.xml +++ b/springboot-annotation/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-annotation diff --git a/springboot-annotation/src/main/java/com/tiesheng/annotation/encrypt/EncryptedRespBody.java b/springboot-annotation/src/main/java/com/tiesheng/annotation/encrypt/EncryptedRespBody.java deleted file mode 100644 index 61828a2..0000000 --- a/springboot-annotation/src/main/java/com/tiesheng/annotation/encrypt/EncryptedRespBody.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tiesheng.annotation.encrypt; - -import java.lang.annotation.*; - -/** - * @author hao - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface EncryptedRespBody { -} diff --git a/springboot-database/pom.xml b/springboot-database/pom.xml index 00342a0..1af4548 100644 --- a/springboot-database/pom.xml +++ b/springboot-database/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-database diff --git a/springboot-encrypt/pom.xml b/springboot-encrypt/pom.xml index dcc85f9..99ed4e9 100644 --- a/springboot-encrypt/pom.xml +++ b/springboot-encrypt/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-encrypt @@ -18,11 +18,6 @@ - - org.bouncycastle - bcprov-jdk15to18 - 1.68 - org.springframework.boot diff --git a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/EnableEncryptConfig.java b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/EnableEncryptConfig.java index b6cbc66..1fd3904 100644 --- a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/EnableEncryptConfig.java +++ b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/EnableEncryptConfig.java @@ -1,20 +1,21 @@ package com.tiesheng.encrypt; -import com.tiesheng.encrypt.config.EncryptConfig; import com.tiesheng.encrypt.config.EncryptRequestBodyAdvice; import com.tiesheng.encrypt.config.EncryptResponseBodyAdvice; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; /** * @author hao */ -@Configuration -@ComponentScan(basePackageClasses = { - EncryptConfig.class, +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({ EncryptRequestBodyAdvice.class, EncryptResponseBodyAdvice.class, }) -public class EnableEncryptConfig { +public @interface EnableEncryptConfig { } diff --git a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptConfig.java b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptConfig.java deleted file mode 100644 index 8bb9b13..0000000 --- a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.tiesheng.encrypt.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * @author hao - */ -@Configuration -@ConfigurationProperties(prefix = "tiesheng.encrypt") -public class EncryptConfig { - - public String publicD; - public String privateQ; - private boolean enable = false; - - /////////////////////////////////////////////////////////////////////////// - // setter\getter - /////////////////////////////////////////////////////////////////////////// - - public boolean isEnable() { - return enable; - } - - public void setEnable(boolean enable) { - this.enable = enable; - } - - public String getPublicD() { - return publicD; - } - - public void setPublicD(String publicD) { - this.publicD = publicD; - } - - public String getPrivateQ() { - return privateQ; - } - - public void setPrivateQ(String privateQ) { - this.privateQ = privateQ; - } -} diff --git a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptRequestBodyAdvice.java b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptRequestBodyAdvice.java index d975037..f517202 100644 --- a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptRequestBodyAdvice.java +++ b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptRequestBodyAdvice.java @@ -3,13 +3,8 @@ package com.tiesheng.encrypt.config; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.ECKeyUtil; -import cn.hutool.crypto.SmUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.json.JSONUtil; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.tiesheng.util.config.EncryptConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; @@ -42,12 +37,8 @@ public class EncryptRequestBodyAdvice implements RequestBodyAdvice { public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { - if (!encryptConfig.isEnable()) { - return inputMessage; - } - try { - return new DecryptHttpInputMessage(inputMessage, encryptConfig.getPrivateQ()); + return new DecryptHttpInputMessage(inputMessage, encryptConfig); } catch (Exception ignore) { } @@ -72,16 +63,12 @@ public class EncryptRequestBodyAdvice implements RequestBodyAdvice { private HttpHeaders headers; private InputStream body; - DecryptHttpInputMessage(HttpInputMessage inputMessage, String privateQ) throws Exception { + DecryptHttpInputMessage(HttpInputMessage inputMessage, EncryptConfig encryptConfig) throws Exception { this.headers = inputMessage.getHeaders(); String bodyStr = IoUtil.read(inputMessage.getBody(), CharsetUtil.CHARSET_UTF_8); String encryptData = JSONUtil.parseObj(bodyStr).getStr("encryptData"); if (!StrUtil.isEmpty(encryptData)) { - // 部分语言加密之后缺少04前缀,如果解密失败,可尝试增加04 - ECPrivateKeyParameters privateKeyParameters = ECKeyUtil.toSm2PrivateParams(privateQ); - ECPublicKeyParameters publicKeyParameters = ECKeyUtil.getPublicParams(privateKeyParameters); - SM2 sm2 = SmUtil.sm2(privateKeyParameters, publicKeyParameters); - String decrypt = sm2.decryptStr(encryptData, KeyType.PrivateKey); + String decrypt = encryptConfig.decrypt(encryptData); this.body = IoUtil.toStream(decrypt, Charset.defaultCharset()); } else { this.body = IoUtil.toStream(bodyStr, Charset.defaultCharset()); diff --git a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptResponseBodyAdvice.java b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptResponseBodyAdvice.java index 80fc27a..b55841e 100644 --- a/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptResponseBodyAdvice.java +++ b/springboot-encrypt/src/main/java/com/tiesheng/encrypt/config/EncryptResponseBodyAdvice.java @@ -2,16 +2,10 @@ package com.tiesheng.encrypt.config; import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.ECKeyUtil; -import cn.hutool.crypto.SmUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.hutool.log.LogFactory; -import com.tiesheng.annotation.encrypt.EncryptedRespBody; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.tiesheng.util.config.EncryptConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; @@ -19,6 +13,7 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @@ -33,22 +28,13 @@ public class EncryptResponseBodyAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class> converterType) { - return true; + return AnnotationUtil.getAnnotation(returnType.getContainingClass(), RestController.class) != null; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { - if (!encryptConfig.isEnable()) { - return body; - } - - boolean encrypt = AnnotationUtil.getAnnotation(returnType.getContainingClass(), EncryptedRespBody.class) != null; - if (!encrypt) { - return body; - } - try { String content = JSONUtil.toJsonStr(body); @@ -62,12 +48,7 @@ public class EncryptResponseBodyAdvice implements ResponseBodyAdvice { JSONObject resp = JSONUtil.parseObj(content); resp.set("encrypted", true); if (resp.getInt("code") == 200) { - // 用公钥进行加密 - ECPrivateKeyParameters privateKeyParameters = ECKeyUtil.toSm2PrivateParams(encryptConfig.getPrivateQ()); - ECPublicKeyParameters publicKeyParameters = ECKeyUtil.getPublicParams(privateKeyParameters); - SM2 sm2 = SmUtil.sm2(privateKeyParameters, publicKeyParameters); - String decrypt = sm2.encryptHex(respData, KeyType.PublicKey); - resp.set("data", decrypt.substring(2)); + resp.set("data", encryptConfig.encrypt(respData)); } return resp; } catch (Exception var17) { diff --git a/springboot-login/pom.xml b/springboot-login/pom.xml index 1cd91aa..d00575d 100644 --- a/springboot-login/pom.xml +++ b/springboot-login/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-login diff --git a/springboot-message/pom.xml b/springboot-message/pom.xml index 4ed9949..d89e5a3 100644 --- a/springboot-message/pom.xml +++ b/springboot-message/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-message diff --git a/springboot-platform/pom.xml b/springboot-platform/pom.xml index c9fa230..1fd3f5a 100644 --- a/springboot-platform/pom.xml +++ b/springboot-platform/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-platform diff --git a/springboot-poi/pom.xml b/springboot-poi/pom.xml index 675c1d3..8d10283 100644 --- a/springboot-poi/pom.xml +++ b/springboot-poi/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-poi diff --git a/springboot-util/pom.xml b/springboot-util/pom.xml index 1aeedd9..0fe7723 100644 --- a/springboot-util/pom.xml +++ b/springboot-util/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-util @@ -37,6 +37,14 @@ fastjson 1.2.78 + + + + org.bouncycastle + bcprov-jdk15to18 + 1.68 + + diff --git a/springboot-util/src/main/java/com/tiesheng/util/PasswordUtils.java b/springboot-util/src/main/java/com/tiesheng/util/PasswordUtils.java deleted file mode 100644 index a761f9e..0000000 --- a/springboot-util/src/main/java/com/tiesheng/util/PasswordUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.tiesheng.util; - -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import com.tiesheng.util.exception.ApiException; - -public class PasswordUtils { - - private static final int PREFIX_SIZE = 8; - - - /** - * 获取加密密码 - * - * @param password - * @return - */ - public static String buildPassword(String password) { - String prefix = RandomUtil.randomString(PREFIX_SIZE); - return prefix + SecureUtil.sha1(password); - } - - /** - * 密码复杂度校验 - * - * @param userPassword - * @return - */ - public static boolean verifyComplexity(String userPassword) { - String password = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$"; - return userPassword.matches(password); - } - - - /** - * 验证密码 - * - * @param userInput - * @param encrypted - * @return - */ - public static void verifyPassword(String userInput, String encrypted) { - String clientIp = ServletKit.getClientIP(); - String userEncrypted = buildPassword(userInput); - - userEncrypted = StrUtil.subSuf(userEncrypted, PREFIX_SIZE); - encrypted = StrUtil.subSuf(encrypted, PREFIX_SIZE); - - if (!StrUtil.equals(userEncrypted, encrypted)) { - int num = NumberUtil.parseInt(TimedCacheHelper.getTimedCache().get(clientIp, false)); - if (num > 5) { - throw new ApiException("登录失败已达6次,请10分钟后再试"); - } - TimedCacheHelper.getTimedCache().put(clientIp, String.valueOf(num + 1), 10 * 60 * 1000); - throw new ApiException("账号或密码错误"); - } - } - - -} diff --git a/springboot-util/src/main/java/com/tiesheng/util/config/EncryptConfig.java b/springboot-util/src/main/java/com/tiesheng/util/config/EncryptConfig.java new file mode 100644 index 0000000..7bf32e4 --- /dev/null +++ b/springboot-util/src/main/java/com/tiesheng/util/config/EncryptConfig.java @@ -0,0 +1,136 @@ +package com.tiesheng.util.config; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.symmetric.SM4; +import com.tiesheng.util.ServletKit; +import com.tiesheng.util.TimedCacheHelper; +import com.tiesheng.util.exception.ApiException; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author hao + */ +@Configuration +@ConfigurationProperties(prefix = "tiesheng.encrypt") +public class EncryptConfig { + + /** + * 加解密对象 + */ + private final SM4 sm4; + + /** + * 加密密钥 + */ + private String key = "WmdUzPJXbngVNiaSsQrihg=="; + private Integer saltSize = 8; + + public EncryptConfig() { + sm4 = SmUtil.sm4(Base64.decode(getKey())); + } + + /** + * 加密 + * + * @param content + * @return + */ + public String encrypt(String content) { + if (StrUtil.isEmpty(content)) { + return ""; + } + return sm4.encryptBase64(content); + } + + + /** + * 解密 + * + * @param base64 + * @return + */ + public String decrypt(String base64) { + try { + return sm4.decryptStr(base64); + } catch (Exception ignore) { + } + return base64; + } + + + /** + * 创建密码 + * + * @param inputPasswd + * @param salt 盐,不存将自动生成 + * @return + */ + public String passwdCreate(String inputPasswd, String salt) { + if (StrUtil.isEmpty(salt)) { + salt = RandomUtil.randomString(saltSize); + } + if (!passwdComplexity(inputPasswd)) { + throw new ApiException("需要包含数字、大小写字母、特殊符号,且长度不低于8位"); + } + return encrypt(salt + SecureUtil.sha1(salt + inputPasswd)); + } + + + /** + * 复杂度校验 + * + * @param inputPasswd + * @return + */ + public boolean passwdComplexity(String inputPasswd) { + String password = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$"; + return inputPasswd.matches(password); + } + + + /** + * 密码校验 + * + * @param inputPasswd + */ + public void passwdVerify(String inputPasswd, String encrypted) { + String salt = decrypt(encrypted).substring(0, saltSize); + String inputEncrypted = passwdCreate(inputPasswd, salt); + if (!StrUtil.equals(inputEncrypted, encrypted)) { + String clientIp = ServletKit.getClientIP(); + int num = NumberUtil.parseInt(TimedCacheHelper.getTimedCache().get(clientIp, false)); + if (num > 5) { + throw new ApiException("登录失败已达6次,请10分钟后再试"); + } + TimedCacheHelper.getTimedCache().put(clientIp, String.valueOf(num + 1), 10 * 60 * 1000); + throw new ApiException("账号或密码错误"); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // setter\getter + /////////////////////////////////////////////////////////////////////////// + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Integer getSaltSize() { + return saltSize; + } + + public void setSaltSize(Integer saltSize) { + this.saltSize = saltSize; + } +} diff --git a/springboot-web/pom.xml b/springboot-web/pom.xml index 0cbdca6..4a9b0b8 100644 --- a/springboot-web/pom.xml +++ b/springboot-web/pom.xml @@ -6,7 +6,7 @@ com.tiesheng.springboot-parent springboot-parent - 0.7.4 + 0.8.0 springboot-web @@ -87,6 +87,11 @@ springboot-message + + com.tiesheng.springboot-parent + springboot-encrypt + +