mirror of
https://github.com/pppscn/SmsForwarder
synced 2024-11-04 06:00:11 +00:00
新增:支持Bark推送加密 #273 (详见:https://bark.day.app/#/encryption)
This commit is contained in:
parent
f5de522967
commit
1bc2668ab2
@ -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
|
@ -46,6 +46,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
private val viewModel by viewModels<SenderViewModel> { 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<FragmentSendersBarkBinding?>(), 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<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
|
||||
//编辑
|
||||
binding!!.btnDel.setText(R.string.del)
|
||||
AppDatabase.getInstance(requireContext())
|
||||
.senderDao()
|
||||
.get(senderId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : SingleObserver<Sender> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
AppDatabase.getInstance(requireContext()).senderDao().get(senderId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<Sender> {
|
||||
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<FragmentSendersBarkBinding?>(), 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<FragmentSendersBarkBinding?>(), 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<FragmentSendersBarkBinding?>(), 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() {
|
||||
|
@ -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
|
||||
|
@ -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<String, Any> = 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("(?<!回复)(验证码|授权码|校验码|检验码|确认码|激活码|动态码|安全码|(验证)?代码|校验代码|检验代码|激活代码|确认代码|动态代码|安全代码|登入码|认证码|识别码|短信口令|动态密码|交易码|上网密码|动态口令|随机码|驗證碼|授權碼|校驗碼|檢驗碼|確認碼|激活碼|動態碼|(驗證)?代碼|校驗代碼|檢驗代碼|確認代碼|激活代碼|動態代碼|登入碼|認證碼|識別碼|一次性密码|[Cc][Oo][Dd][Ee]|[Vv]erification)")
|
||||
if (pattern.containsMatchIn(content)) {
|
||||
var code = content.replace("(.*)((代|授权|验证|动态|校验)码|[【\\[].*[】\\]]|[Cc][Oo][Dd][Ee]|[Vv]erification\\s?([Cc]ode)?)\\s?(G-|<#>)?([::\\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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -303,6 +303,74 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/senderBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dp"
|
||||
android:text="@string/bark_encryption_algorithm"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner
|
||||
android:id="@+id/sp_encryption_algorithm"
|
||||
style="@style/Material.SpinnerStyle"
|
||||
android:layout_marginStart="5dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/senderBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bark_encryption_key"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
|
||||
android:id="@+id/et_encryption_key"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/bark_encryption_key_tips"
|
||||
android:singleLine="true"
|
||||
app:met_clearButton="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/senderBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bark_encryption_iv"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
|
||||
android:id="@+id/et_encryption_iv"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/bark_encryption_iv_tips"
|
||||
android:singleLine="true"
|
||||
app:met_clearButton="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
@ -728,12 +728,16 @@
|
||||
<string name="bark_url_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]]]></string>
|
||||
<string name="bark_url_regex2" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^[a-z]+://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]]]></string>
|
||||
<string name="bark_url_error">Url format error</string>
|
||||
<string name="bark_key">AES Key</string>
|
||||
<string name="bark_key_tips">Fill in 16 chars to enable push encryption</string>
|
||||
<string name="bark_iv">AES iv</string>
|
||||
<string name="bark_iv_tips">Fill in 16 chars to enable push encryption</string>
|
||||
<string name="bark_key_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^[a-zA-Z0-9]{16}]]></string>
|
||||
<string name="bark_key_error">AES Key and iv must be 16 characters</string>
|
||||
<string name="bark_encryption_algorithm">Encryption Algorithm</string>
|
||||
<string name="bark_encryption_algorithm_none">NONE</string>
|
||||
<string name="bark_encryption_key">AES Key</string>
|
||||
<string name="bark_encryption_key_tips">corresponding key on bark</string>
|
||||
<string name="bark_encryption_iv">AES iv</string>
|
||||
<string name="bark_encryption_iv_tips">corresponding iv on bark</string>
|
||||
<string name="bark_encryption_key_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^[a-zA-Z0-9]{16}]]></string>
|
||||
<string name="bark_encryption_key_error1">AES Key and iv must be 16 characters</string>
|
||||
<string name="bark_encryption_key_error2">AES Key and iv must be 24 characters</string>
|
||||
<string name="bark_encryption_key_error3">AES Key and iv must be 32 characters</string>
|
||||
|
||||
<string name="from_email_hint">Fill in the username before @</string>
|
||||
<string name="from_email_full_hint">Fill in the format: AAA@BBB.CCC</string>
|
||||
|
@ -729,12 +729,16 @@
|
||||
<string name="bark_url_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]]]></string>
|
||||
<string name="bark_url_regex2" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^[a-z]+://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]]]></string>
|
||||
<string name="bark_url_error">Url格式错误</string>
|
||||
<string name="bark_key">加密密钥</string>
|
||||
<string name="bark_key_tips">填写16个字符,以启用推送加密</string>
|
||||
<string name="bark_iv">偏移向量</string>
|
||||
<string name="bark_iv_tips">填写16个字符,以启用推送加密</string>
|
||||
<string name="bark_key_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^([a-zA-Z0-9]{16})? $]]></string>
|
||||
<string name="bark_key_error">加密密钥和偏移向量必须同时是16个字符</string>
|
||||
<string name="bark_encryption_algorithm">加密算法</string>
|
||||
<string name="bark_encryption_algorithm_none">不加密</string>
|
||||
<string name="bark_encryption_key">加密密钥</string>
|
||||
<string name="bark_encryption_key_tips">对应bark上的key</string>
|
||||
<string name="bark_encryption_iv">偏移向量</string>
|
||||
<string name="bark_encryption_iv_tips">对应bark上的iv</string>
|
||||
<string name="bark_encryption_key_regex" formatted="false" tools:ignore="TypographyDashes"><![CDATA[^([a-zA-Z0-9]{16})? $]]></string>
|
||||
<string name="bark_encryption_key_error1">加密密钥和偏移向量都必须是16位</string>
|
||||
<string name="bark_encryption_key_error2">加密密钥和偏移向量都必须是24位</string>
|
||||
<string name="bark_encryption_key_error3">加密密钥和偏移向量都必须是32位</string>
|
||||
|
||||
<string name="from_email_hint">填写 @ 前面的用户名</string>
|
||||
<string name="from_email_full_hint">填写格式: AAA@BBB.CCC</string>
|
||||
|
Loading…
Reference in New Issue
Block a user