生成 WSSE-Header 时使用 Koltin 访问 ORO-Platform API 失败
Access to ORO-Platform API with Koltin fails while generating WSSE-Header
鉴于此 PHP-file 生成 WSSE-Header 以访问 ORO-Platform API (https://oroinc.com/oroplatform/). Official documentation for building the WSSE-Header: https://oroinc.com/orocrm/doc/2.3/dev-guide/cookbook/how-to-use-wsse-authentication#how-to-use-wsse-authentication
<?php
$userName = 'admin';
$userApiKey = 'dfeeb001cc4b947d8790c906d196d78a41915749';
$nonce = base64_encode(substr(md5(uniqid()), 0, 16));
$created = date('c');
// Just for test for StakeOverFlow
created = "2019-02-05T14:45:43+01:00"
nonce = "REBxdWVYkSrAADwORS+H5w=="
$digest = base64_encode(sha1(base64_decode($nonce) . $created . $userApiKey, true));
$wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
$wsseHeader.= sprintf(
'X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
$userName,
$digest,
$nonce,
$created
);
echo $wsseHeader;
我需要在 Kotlin 中为 Android 应用程序使用相同的逻辑。
到目前为止,这是我的代码
package de.warehouse.prozessio.appwarehouse.manager
import android.util.Base64
import java.util.*
import android.util.Log
import java.security.MessageDigest
import java.text.SimpleDateFormat
class OroAPIManager {
fun getHeader() {
val md = MessageDigest.getInstance("SHA-1")
val user = "admin"
val key = "dfeeb001cc4b947d8790c906d196d78a41915749"
var nonce = String(Base64.encode(md.digest(UUID.randomUUID().toString().toByteArray()), 0, 16,0)).dropLast(1)
var created = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMAN).format(Date())
created = created.replaceRange(created.length - 2, created.length - 1, ":0")
// Just for test for Whosebug
created = "2019-02-06T15:37:11+01:00"
nonce = "NjA3Yzc4ZjEwY2ZjNTY4NA=="
val digest = Base64.encode((convertToSha1String(String(Base64.decode(nonce.toByteArray(), Base64.DEFAULT)) + created + key)).toByteArray(), Base64.DEFAULT)
var wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
wsseHeader += "X-WSSE: UsernameToken Username=\"$user\", "
wsseHeader += "PasswordDigest=\"${String(digest).dropLast(1)}\", "
wsseHeader += "Nonce=\"$nonce\", "
wsseHeader += "Created=\"$created\""
Log.i("data", wsseHeader)
}
private fun convertToSha1String(raw: String): String {
var convertedString = "";
val bytes = raw.toByteArray()
val md = MessageDigest.getInstance("SHA-1")
val digest = md.digest(bytes)
for (byte in digest) convertedString += "Cn".format(byte)
// for (byte in digest) convertedString += byte.toChar()
// for (byte in digest) convertedString += byte.toString()
Log.i("data", "Converted String: $convertedString")
return convertedString
}
}
PHP 脚本的结果是:
X-WSSE: UsernameToken Username="admin", PasswordDigest="fukhjBVWYl0i08iPaLBeXTJkT4U=", Nonce="NjA3Yzc4ZjEwY2ZjNTY4NA==", Created="2019-02-06T15:37:11+01:00"
这是正确的,我可以访问 Oro。
Kotlin 的结果是:
X-WSSE: UsernameToken Username="admin", PasswordDigest="Q25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25Dbg==", Nonce="NjA3Yzc4ZjEwY2ZjNTY4NA==", Created="2019-02-06T15:37:11+01:00"
这是不正确的。我猜的问题是 raw-Flag for PHP-sha1 函数。当我尝试几种方法将 Bytearray 转换为原始字符串但没有成功时。
Oro-Forum 中的主题如下:https://forum.oroinc.com/oro-platform/how-do-i-questions/topic/generate-api-wss-header-for-kotlin#post-37936。他们那里很友好,但是这次没能提供帮助。
有人知道如何将两个不同的结果放在一起吗?
编辑:
感谢@Michael Bessolov 为我指明了正确的方向。这是从 Android 和 ORO-API:
授予访问权限的完整解决方案
import android.content.Context
import android.preference.PreferenceManager
import android.util.Base64
import java.util.*
import java.nio.charset.Charset
import java.security.MessageDigest
import java.text.SimpleDateFormat
class OroAPIManager constructor(val context: Context) {
fun getHeader(): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(this.context)
val user = prefs.getString("oro_user", "<unset>")
val key = prefs.getString("oro_api_key", "<unset>")
val uid = UUID.randomUUID().toString();
val nonce = String(Base64.encode(convertToDigestString(uid, "MD5").toByteArray(), 0, 16,0)).dropLast(1)
var created = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY).format(Date())
created = created.replaceRange(created.length - 2, created.length - 1, ":0")
val nonceBase64 = String(Base64.decode(nonce.toByteArray(), Base64.DEFAULT))
val sha1 = hexToBin(convertToDigestString(nonceBase64 + created + key, "SHA-1"))
val digest = Base64.encode(sha1.toByteArray(Charset.forName("ISO-8859-1")), Base64.DEFAULT)
var wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
wsseHeader += "X-WSSE: UsernameToken Username=\"$user\", "
wsseHeader += "PasswordDigest=\"${String(digest).dropLast(1)}\", "
wsseHeader += "Nonce=\"$nonce\", "
wsseHeader += "Created=\"$created\""
return wsseHeader;
}
private fun convertToDigestString(raw: String, digestName: String): String {
val bytes = raw.toByteArray()
val md = MessageDigest.getInstance(digestName)
val digest = md.digest(bytes)
val hexChars = "0123456789abcdef"
val convertedString = StringBuilder(digest.size * 2)
digest.forEach {
val i = it.toInt()
convertedString.append(hexChars[i shr 4 and 0x0f])
convertedString.append(hexChars[i and 0x0f])
}
return convertedString.toString()
}
private fun hexToBin(hexStr: String): String {
val output = StringBuilder("")
var i = 0
while (i < hexStr.length) {
val str = hexStr.substring(i, i + 2)
output.append(Integer.parseInt(str, 16).toChar())
i += 2
}
return output.toString()
}
}
我一点也不熟悉 Kotlin,但 convertedString += "Cn".format(byte)
在当今的多字节编码世界中看起来很可疑。以下文章(受 https://www.samclarke.com/kotlin-hash-strings/ 启发)会产生不同的结果吗?
val HEX_CHARS = "0123456789ABCDEF"
val convertedString = StringBuilder(digest.size * 2)
digest.forEach {
val i = it.toInt()
convertedString.append(HEX_CHARS[i shr 4 and 0x0f])
convertedString.append(HEX_CHARS[i and 0x0f])
}
鉴于此 PHP-file 生成 WSSE-Header 以访问 ORO-Platform API (https://oroinc.com/oroplatform/). Official documentation for building the WSSE-Header: https://oroinc.com/orocrm/doc/2.3/dev-guide/cookbook/how-to-use-wsse-authentication#how-to-use-wsse-authentication
<?php
$userName = 'admin';
$userApiKey = 'dfeeb001cc4b947d8790c906d196d78a41915749';
$nonce = base64_encode(substr(md5(uniqid()), 0, 16));
$created = date('c');
// Just for test for StakeOverFlow
created = "2019-02-05T14:45:43+01:00"
nonce = "REBxdWVYkSrAADwORS+H5w=="
$digest = base64_encode(sha1(base64_decode($nonce) . $created . $userApiKey, true));
$wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
$wsseHeader.= sprintf(
'X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
$userName,
$digest,
$nonce,
$created
);
echo $wsseHeader;
我需要在 Kotlin 中为 Android 应用程序使用相同的逻辑。
到目前为止,这是我的代码
package de.warehouse.prozessio.appwarehouse.manager
import android.util.Base64
import java.util.*
import android.util.Log
import java.security.MessageDigest
import java.text.SimpleDateFormat
class OroAPIManager {
fun getHeader() {
val md = MessageDigest.getInstance("SHA-1")
val user = "admin"
val key = "dfeeb001cc4b947d8790c906d196d78a41915749"
var nonce = String(Base64.encode(md.digest(UUID.randomUUID().toString().toByteArray()), 0, 16,0)).dropLast(1)
var created = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMAN).format(Date())
created = created.replaceRange(created.length - 2, created.length - 1, ":0")
// Just for test for Whosebug
created = "2019-02-06T15:37:11+01:00"
nonce = "NjA3Yzc4ZjEwY2ZjNTY4NA=="
val digest = Base64.encode((convertToSha1String(String(Base64.decode(nonce.toByteArray(), Base64.DEFAULT)) + created + key)).toByteArray(), Base64.DEFAULT)
var wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
wsseHeader += "X-WSSE: UsernameToken Username=\"$user\", "
wsseHeader += "PasswordDigest=\"${String(digest).dropLast(1)}\", "
wsseHeader += "Nonce=\"$nonce\", "
wsseHeader += "Created=\"$created\""
Log.i("data", wsseHeader)
}
private fun convertToSha1String(raw: String): String {
var convertedString = "";
val bytes = raw.toByteArray()
val md = MessageDigest.getInstance("SHA-1")
val digest = md.digest(bytes)
for (byte in digest) convertedString += "Cn".format(byte)
// for (byte in digest) convertedString += byte.toChar()
// for (byte in digest) convertedString += byte.toString()
Log.i("data", "Converted String: $convertedString")
return convertedString
}
}
PHP 脚本的结果是:
X-WSSE: UsernameToken Username="admin", PasswordDigest="fukhjBVWYl0i08iPaLBeXTJkT4U=", Nonce="NjA3Yzc4ZjEwY2ZjNTY4NA==", Created="2019-02-06T15:37:11+01:00"
这是正确的,我可以访问 Oro。
Kotlin 的结果是:
X-WSSE: UsernameToken Username="admin", PasswordDigest="Q25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25DbkNuQ25Dbg==", Nonce="NjA3Yzc4ZjEwY2ZjNTY4NA==", Created="2019-02-06T15:37:11+01:00"
这是不正确的。我猜的问题是 raw-Flag for PHP-sha1 函数。当我尝试几种方法将 Bytearray 转换为原始字符串但没有成功时。
Oro-Forum 中的主题如下:https://forum.oroinc.com/oro-platform/how-do-i-questions/topic/generate-api-wss-header-for-kotlin#post-37936。他们那里很友好,但是这次没能提供帮助。
有人知道如何将两个不同的结果放在一起吗?
编辑:
感谢@Michael Bessolov 为我指明了正确的方向。这是从 Android 和 ORO-API:
授予访问权限的完整解决方案import android.content.Context
import android.preference.PreferenceManager
import android.util.Base64
import java.util.*
import java.nio.charset.Charset
import java.security.MessageDigest
import java.text.SimpleDateFormat
class OroAPIManager constructor(val context: Context) {
fun getHeader(): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(this.context)
val user = prefs.getString("oro_user", "<unset>")
val key = prefs.getString("oro_api_key", "<unset>")
val uid = UUID.randomUUID().toString();
val nonce = String(Base64.encode(convertToDigestString(uid, "MD5").toByteArray(), 0, 16,0)).dropLast(1)
var created = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY).format(Date())
created = created.replaceRange(created.length - 2, created.length - 1, ":0")
val nonceBase64 = String(Base64.decode(nonce.toByteArray(), Base64.DEFAULT))
val sha1 = hexToBin(convertToDigestString(nonceBase64 + created + key, "SHA-1"))
val digest = Base64.encode(sha1.toByteArray(Charset.forName("ISO-8859-1")), Base64.DEFAULT)
var wsseHeader = "Authorization: WSSE profile=\"UsernameToken\"\n";
wsseHeader += "X-WSSE: UsernameToken Username=\"$user\", "
wsseHeader += "PasswordDigest=\"${String(digest).dropLast(1)}\", "
wsseHeader += "Nonce=\"$nonce\", "
wsseHeader += "Created=\"$created\""
return wsseHeader;
}
private fun convertToDigestString(raw: String, digestName: String): String {
val bytes = raw.toByteArray()
val md = MessageDigest.getInstance(digestName)
val digest = md.digest(bytes)
val hexChars = "0123456789abcdef"
val convertedString = StringBuilder(digest.size * 2)
digest.forEach {
val i = it.toInt()
convertedString.append(hexChars[i shr 4 and 0x0f])
convertedString.append(hexChars[i and 0x0f])
}
return convertedString.toString()
}
private fun hexToBin(hexStr: String): String {
val output = StringBuilder("")
var i = 0
while (i < hexStr.length) {
val str = hexStr.substring(i, i + 2)
output.append(Integer.parseInt(str, 16).toChar())
i += 2
}
return output.toString()
}
}
我一点也不熟悉 Kotlin,但 convertedString += "Cn".format(byte)
在当今的多字节编码世界中看起来很可疑。以下文章(受 https://www.samclarke.com/kotlin-hash-strings/ 启发)会产生不同的结果吗?
val HEX_CHARS = "0123456789ABCDEF"
val convertedString = StringBuilder(digest.size * 2)
digest.forEach {
val i = it.toInt()
convertedString.append(HEX_CHARS[i shr 4 and 0x0f])
convertedString.append(HEX_CHARS[i and 0x0f])
}