From 1bc2668ab2983dc5cfd8b2b5f97ab3026fc7429b Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Sat, 13 May 2023 23:32:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=94=AF=E6=8C=81Ba?= =?UTF-8?q?rk=E6=8E=A8=E9=80=81=E5=8A=A0=E5=AF=86=20#273=20(=E8=AF=A6?= =?UTF-8?q?=E8=A7=81=EF=BC=9Ahttps://bark.day.app/#/encryption)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../forwarder/entity/setting/BarkSetting.kt | 6 + .../fragment/senders/BarkFragment.kt | 114 +++++++++++------- .../idormy/sms/forwarder/utils/Constants.kt | 9 ++ .../sms/forwarder/utils/sender/BarkUtils.kt | 64 +++++++--- .../main/res/layout/fragment_senders_bark.xml | 68 +++++++++++ app/src/main/res/values-en/strings.xml | 16 ++- app/src/main/res/values/strings.xml | 16 ++- 7 files changed, 216 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt index d9c2af15..b03ab79e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt @@ -19,4 +19,10 @@ data class BarkSetting( val level: String? = "active", //标题模板 val title: String? = "", + //加密算法 + val transformation: String = "none", + //加密密钥 + val key: String = "", + //初始偏移向量 + val iv: String = "", ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt index 3359e067..747feeaa 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt @@ -46,6 +46,7 @@ class BarkFragment : BaseFragment(), View.OnClickLi private val viewModel by viewModels { BaseViewModelFactory(context) } private var mCountDownHelper: CountDownButtonHelper? = null private var barkLevel: String = "active" //通知级别 + private var transformation: String = "none" //加密算法 @JvmField @AutoWired(name = KEY_SENDER_ID) @@ -103,6 +104,18 @@ class BarkFragment : BaseFragment(), View.OnClickLi } binding!!.spLevel.selectedIndex = 0 + binding!!.spEncryptionAlgorithm.setItems(BARK_ENCRYPTION_ALGORITHM_MAP.values.toList()) + binding!!.spEncryptionAlgorithm.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any -> + BARK_ENCRYPTION_ALGORITHM_MAP.forEach { + if (it.value == item) transformation = it.key + } + } + binding!!.spEncryptionAlgorithm.setOnNothingSelectedListener { + binding!!.spEncryptionAlgorithm.selectedIndex = 0 + transformation = "none" + } + binding!!.spEncryptionAlgorithm.selectedIndex = 0 + //新增 if (senderId <= 0) { titleBar?.setSubTitle(getString(R.string.add_sender)) @@ -112,43 +125,43 @@ class BarkFragment : BaseFragment(), View.OnClickLi //编辑 binding!!.btnDel.setText(R.string.del) - AppDatabase.getInstance(requireContext()) - .senderDao() - .get(senderId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : SingleObserver { - override fun onSubscribe(d: Disposable) {} + AppDatabase.getInstance(requireContext()).senderDao().get(senderId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} - override fun onError(e: Throwable) { - e.printStackTrace() - } + override fun onError(e: Throwable) { + e.printStackTrace() + } - override fun onSuccess(sender: Sender) { - if (isClone) { - titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) - binding!!.btnDel.setText(R.string.discard) - } else { - titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) - } - binding!!.etName.setText(sender.name) - binding!!.sbEnable.isChecked = sender.status == 1 - val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) - Log.d(TAG, settingVo.toString()) - if (settingVo != null) { - binding!!.etServer.setText(settingVo.server) - binding!!.etGroup.setText(settingVo.group) - binding!!.etIcon.setText(settingVo.icon) - binding!!.etSound.setText(settingVo.sound) - binding!!.etBadge.setText(settingVo.badge) - binding!!.etUrl.setText(settingVo.url) - BARK_LEVEL_MAP.forEach { - if (it.key == settingVo.level) binding!!.spLevel.setSelectedItem(it.value) - } - binding!!.etTitleTemplate.setText(settingVo.title) - } + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) } - }) + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etServer.setText(settingVo.server) + binding!!.etGroup.setText(settingVo.group) + binding!!.etIcon.setText(settingVo.icon) + binding!!.etSound.setText(settingVo.sound) + binding!!.etBadge.setText(settingVo.badge) + binding!!.etUrl.setText(settingVo.url) + BARK_LEVEL_MAP.forEach { + if (it.key == settingVo.level) binding!!.spLevel.setSelectedItem(it.value) + } + binding!!.etTitleTemplate.setText(settingVo.title) + BARK_ENCRYPTION_ALGORITHM_MAP.forEach { + if (it.value == settingVo.transformation) binding!!.spEncryptionAlgorithm.setSelectedItem(it.value) + } + binding!!.etEncryptionKey.setText(settingVo.key) + binding!!.etEncryptionIv.setText(settingVo.iv) + } + } + }) } @@ -172,18 +185,22 @@ class BarkFragment : BaseFragment(), View.OnClickLi CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) return } + R.id.bt_insert_extra -> { CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) return } + R.id.bt_insert_time -> { CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) return } + R.id.bt_insert_device_name -> { CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) return } + R.id.btn_test -> { mCountDownHelper?.start() Thread { @@ -202,25 +219,21 @@ class BarkFragment : BaseFragment(), View.OnClickLi }.start() return } + R.id.btn_del -> { if (senderId <= 0 || isClone) { popToBack() return } - MaterialDialog.Builder(requireContext()) - .title(R.string.delete_sender_title) - .content(R.string.delete_sender_tips) - .positiveText(R.string.lab_yes) - .negativeText(R.string.lab_no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - viewModel.delete(senderId) - XToastUtils.success(R.string.delete_sender_toast) - popToBack() - } - .show() + MaterialDialog.Builder(requireContext()).title(R.string.delete_sender_title).content(R.string.delete_sender_tips).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + }.show() return } + R.id.btn_save -> { val name = binding!!.etName.text.toString().trim() if (TextUtils.isEmpty(name)) { @@ -262,8 +275,17 @@ class BarkFragment : BaseFragment(), View.OnClickLi throw Exception(getString(R.string.invalid_bark_url)) } val title = binding!!.etTitleTemplate.text.toString().trim() + val key = binding!!.etEncryptionKey.text.toString().trim() + val iv = binding!!.etEncryptionIv.text.toString().trim() + if (transformation.startsWith("AES128") && (key.length != 16 || iv.length != 16)) { + throw Exception(getString(R.string.bark_encryption_key_error1)) + } else if (transformation.startsWith("AES192") && (key.length != 24 || iv.length != 24)) { + throw Exception(getString(R.string.bark_encryption_key_error2)) + } else if (transformation.startsWith("AES256") && (key.length != 32 || iv.length != 32)) { + throw Exception(getString(R.string.bark_encryption_key_error3)) + } - return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title) + return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title, transformation, key, iv) } override fun onDestroyView() { diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index f0ad5978..7ebb7f6b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -167,6 +167,15 @@ val BARK_LEVEL_MAP = mapOf( "timeSensitive" to getString(R.string.bark_level_timeSensitive), "passive" to getString(R.string.bark_level_passive) ) +val BARK_ENCRYPTION_ALGORITHM_MAP = mapOf( + "none" to getString(R.string.bark_encryption_algorithm_none), + "AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding", + "AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding", + "AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding", + "AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding", + "AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding", + "AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding", +) //发送通道 const val TYPE_DINGTALK_GROUP_ROBOT = 0 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt index 1c161e40..f0da22e4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt @@ -1,6 +1,7 @@ package com.idormy.sms.forwarder.utils.sender import android.text.TextUtils +import android.util.Base64 import android.util.Log import com.google.gson.Gson import com.idormy.sms.forwarder.database.entity.Rule @@ -13,8 +14,13 @@ import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.exception.ApiException +import java.net.URLEncoder import java.util.regex.Pattern +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +@Suppress("unused") class BarkUtils { companion object { @@ -52,29 +58,40 @@ class BarkUtils { XHttp.post(requestUrl) } - request.params("title", title) - .params("body", content) - .params("isArchive", 1) - if (!TextUtils.isEmpty(setting.group)) request.params("group", setting.group) - if (!TextUtils.isEmpty(setting.icon)) request.params("icon", setting.icon) - if (!TextUtils.isEmpty(setting.level)) request.params("level", setting.level) - if (!TextUtils.isEmpty(setting.sound)) request.params("sound", setting.sound) - if (!TextUtils.isEmpty(setting.badge)) request.params("badge", setting.badge) - if (!TextUtils.isEmpty(setting.url)) request.params("url", setting.url) + val msgMap: MutableMap = mutableMapOf() + msgMap["title"] = title + msgMap["body"] = content + msgMap["isArchive"] = 1 + if (!TextUtils.isEmpty(setting.group)) msgMap["group"] = setting.group.toString() + if (!TextUtils.isEmpty(setting.icon)) msgMap["icon"] = setting.icon.toString() + if (!TextUtils.isEmpty(setting.level)) msgMap["level"] = setting.level.toString() + if (!TextUtils.isEmpty(setting.sound)) msgMap["sound"] = setting.sound.toString() + if (!TextUtils.isEmpty(setting.badge)) msgMap["badge"] = setting.badge.toString() + if (!TextUtils.isEmpty(setting.url)) msgMap["url"] = setting.url.toString() - val isCode: Int = content.indexOf("验证码") - val isPassword: Int = content.indexOf("动态密码") - val isPassword2: Int = content.indexOf("短信密码") - if (isCode != -1 || isPassword != -1 || isPassword2 != -1) { - val p = Pattern.compile("(\\d{4,6})") - val m = p.matcher(content) - if (m.find()) { - println(m.group()) - request.params("automaticallyCopy", "1") - request.params("copy", m.group()) + //自动复制验证码 + val pattern = Regex("(?)?([::\\s是为]|[Ii][Ss]){0,3}[\\((\\[【{「]?(([0-9\\s]{4,7})|([\\dA-Za-z]{5,6})(?!([Vv]erification)?([Cc][Oo][Dd][Ee])|:))[」}】\\])\\)]?(?=([^0-9a-zA-Z]|\$))(.*)".toRegex(), "$7").trim() + code = code.replace("[^\\d]*[\\((\\[【{「]?([0-9]{3}\\s?[0-9]{1,3})[」}】\\])\\)]?(?=.*((代|授权|验证|动态|校验)码|[【\\[].*[】\\]]|[Cc][Oo][Dd][Ee]|[Vv]erification\\s?([Cc]ode)?))(.*)".toRegex(), "$1").trim() + if (code.isNotEmpty()) { + msgMap["copy"] = code + msgMap["automaticallyCopy"] = 1 } } + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + //推送加密 + if (setting.transformation.isNotEmpty() && "none" != setting.transformation && setting.key.isNotEmpty() && setting.iv.isNotEmpty()) { + var transformation = setting.transformation.replace("AES128", "AES").replace("AES192", "AES").replace("AES256", "AES") + val ciphertext = encrypt(requestMsg, transformation, setting.key, setting.iv) + request.params("ciphertext", URLEncoder.encode(ciphertext, "UTF-8")) + request.params("iv", URLEncoder.encode(setting.iv, "UTF-8")) + } else { + request.upJson(requestMsg) + } + request.ignoreHttpsCert() //忽略https证书 .keepJson(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s @@ -106,5 +123,14 @@ class BarkUtils { } + fun encrypt(plainText: String, transformation: String, key: String, iv: String): String { + val cipher = Cipher.getInstance(transformation) + val keySpec = SecretKeySpec(key.toByteArray(), "AES") + val ivSpec = IvParameterSpec(iv.toByteArray()) + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) + val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8)) + return Base64.encode(encryptedBytes, Base64.NO_WRAP).toString() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_senders_bark.xml b/app/src/main/res/layout/fragment_senders_bark.xml index e77cb9b1..4620948f 100644 --- a/app/src/main/res/layout/fragment_senders_bark.xml +++ b/app/src/main/res/layout/fragment_senders_bark.xml @@ -303,6 +303,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 67bf25b6..ace6c6cd 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -728,12 +728,16 @@ Url format error - AES Key - Fill in 16 chars to enable push encryption - AES iv - Fill in 16 chars to enable push encryption - - AES Key and iv must be 16 characters + Encryption Algorithm + NONE + AES Key + corresponding key on bark + AES iv + corresponding iv on bark + + AES Key and iv must be 16 characters + AES Key and iv must be 24 characters + AES Key and iv must be 32 characters Fill in the username before @ Fill in the format: AAA@BBB.CCC diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b7981e19..bc968ade 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -729,12 +729,16 @@ Url格式错误 - 加密密钥 - 填写16个字符,以启用推送加密 - 偏移向量 - 填写16个字符,以启用推送加密 - - 加密密钥和偏移向量必须同时是16个字符 + 加密算法 + 不加密 + 加密密钥 + 对应bark上的key + 偏移向量 + 对应bark上的iv + + 加密密钥和偏移向量都必须是16位 + 加密密钥和偏移向量都必须是24位 + 加密密钥和偏移向量都必须是32位 填写 @ 前面的用户名 填写格式: AAA@BBB.CCC