WS-Security 在 OpenEdge 中,继续
WS-Security in OpenEdge, continued
作为这个问题的跟进:Implementing WS-Security in Progress ABL,我将继续努力在 Progress OpenEdge 中实施 WS-Security。
我的问题:
在对特定 Web 服务的每个请求中,我都会根据以下内容生成密码摘要:
- A "nonce" - 随机字符串
- 一个时间戳 - 当前时间
- 密码 - 我和网络服务提供商之间的共享秘密。
然后将 nonce、时间戳和摘要添加到 Web 服务调用的 Soap header。
这在大多数情况下工作正常,但在 100 个请求中大约有 5 个失败(请参阅下面的更多信息)。
这是我生成摘要的方式:
PROCEDURE generatePassHashNonceClear:
/*------------------------------------------------------------------------------
Purpose:
Generates a password hash for WS-Security
General algorithm:
Digest = base64(sha1(Nonce + Timestamp + sha1(Pwd)))
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER pcNonce AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcCreated AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcPassword AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcHash AS CHARACTER NO-UNDO.
DEFINE VARIABLE mBytes AS MEMPTR NO-UNDO.
DEFINE VARIABLE mSHA1 AS MEMPTR NO-UNDO.
/*
Set size of mempointer, add 20 since we are adding the 20 byte
SHA1-DIGEST of the clear password in the end.
*/
SET-SIZE(mBytes) = LENGTH(pcNonce) + LENGTH(pcCreated) + 20.
/* Put the decoded nonce first */
PUT-STRING(mBytes, 1) = pcNonce.
/* Add create time */
PUT-STRING(mBytes, 1 + LENGTH(pcNonce)) = pcCreated.
/* Set SHA1 returns a 20 byte raw string. */
SET-SIZE(mSHA1) = 20.
mSHA1 = SHA1-DIGEST(pcPassword).
/* Add password, SHA1-digested (so we need to put bytes instead of a string */
PUT-BYTES(mBytes, 1 + LENGTH(pcNonce) + LENGTH(pcCreated)) = mSHA1.
/* Create out-data in B64-encoded format */
pcHash = STRING(BASE64-ENCODE(SHA1-DIGEST(mBytes))).
/* Clean up mempointers */
SET-SIZE(mBytes) = 0.
SET-SIZE(mSHA1) = 0.
END PROCEDURE.
程序是这样调用的:
DEFINE VARIABLE cPasswordClear AS CHARACTER NO-UNDO.
DEFINE VARIABLE dtZuluNow AS DATETIME NO-UNDO.
DEFINE VARIABLE cCreated AS CHARACTER NO-UNDO.
DEFINE VARIABLE cNonceB64 AS CHARACTER NO-UNDO.
DEFINE VARIABLE cNonce AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPasswordDigest AS CHARACTER NO-UNDO.
/*
Get time in UTC/GMT/ZULU/Timezone 0 and store
it with 000 as milliseconds + Z for timezone Zulu
Nonce is a random generated string
*/
ASSIGN
dtZuluNow = DATETIME-TZ(NOW,0)
cCreated = STRING(dtZuluNow, "9999-99-99THH:MM:SS") + ":000Z"
cPasswordClear = "SECRET"
cNonceB64 = BASE64-ENCODE(GENERATE-RANDOM-KEY)
cNonce = STRING(BASE64-DECODE(cNonceB64)).
RUN generatePassHashNonceClear( cNonce, cCreated, cPasswordClear, OUTPUT cPasswordDigest).
我知道的:
这在大约 10 000 个请求中的 9 500 个中工作正常。但是有5%的失败率。不幸的是,错误消息没有帮助,所以我真正能看到的是登录失败。 Web 服务提供商声明登录因不正确的摘要而被拒绝。
我做了什么:
为了测试我的摘要程序,我创建了一个小 python 程序。当我尝试使用失败登录的输入数据(nonce 和时间戳)时,这确实会创建不同的摘要。然而,我不是 Python 程序员,所以这个程序很可能有问题(但它也应该在相同的 95% 的情况下工作,这将是一个非常奇怪的巧合)。
这是 python 程序:
import hashlib
def createDigest(Nonce, Created, Password):
"This function returns a digest"
NonceB64 = Nonce.decode("base64","strict")
pdgst = hashlib.sha1()
pdgst.update(Password)
PasswordDgst = pdgst.digest()
FinalDgst = hashlib.sha1()
FinalDgst.update(NonceB64)
FinalDgst.update(Created)
FinalDgst.update(PasswordDgst)
FinalTxt = FinalDgst.digest().encode("base64","strict")
print "Final digest : " + FinalTxt
return
print "This digest is repeated in Progress OpenEdge"
createDigest("tGxF8+DAmJvQo93PNZt5Nw==", "2015-04-08T20:10:44:000Z", "SECRET")
print "This digest isn't repeated in Progress OpenEdge"
createDigest("XdcAW1TdTr+MLp4t0QkJ8g==", "2015-04-08T20:10:44:000Z", "SECRET")
我的真实密码当然不是"SECRET",这让我相信错误与随机数有关。将密码更改为 "SECRET" 会使摘要不同,但之后 Progress 和 Python 摘要之间的差异仍然存在(上面的第一个示例在更改前后生成了相似的摘要,但第二个没有)。
我有一个关于 Progress Support 的公开案例,但他们似乎和我一样为此苦苦挣扎。
我已经在 RHEL 和 Windows 7 上的 OpenEdge 11.3.1 和 11.4 中对此进行了测试,并且行为保持不变。
回答我自己的问题以供将来参考:
正如@TomBascom 指出的那样,问题与代码页转换有关,但实际错误确实在 "chain" 中早于 SHA 消化。
cNonceB64 = BASE64-ENCODE(GENERATE-RANDOM-KEY)
cNonce = STRING(BASE64-DECODE(cNonceB64))
在第二行中,只要生成的密钥包含 iso8859-1 和 UTF-8 之间不匹配的值,cNonce 的值就会被销毁。
简单的解决方案是将 cNonce 变量更改为内存指针,然后重写生成摘要的过程。
/* Optimistic, should really be based on current symmetric encryption algorithm */
SET-SIZE(mNonce) = 16.
ASSIGN
mNonce = GENERATE-RANDOM-KEY
cNonceB64 = BASE64-ENCODE(mNonce).
然后是生成密码摘要的新程序:
PROCEDURE generateDigest:
/*------------------------------------------------------------------------------
Purpose: Generates a password hash for WS-Security
Parameters: <none>
Notes:
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER mNonce AS MEMPTR NO-UNDO.
DEFINE INPUT PARAMETER pcCreated AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcPassword AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcHash AS CHARACTER NO-UNDO.
DEFINE VARIABLE mBytes AS MEMPTR NO-UNDO.
DEFINE VARIABLE mSHA1 AS MEMPTR NO-UNDO.
/*
Set size of mempointer, add 20 since we are adding the 20 byte
SHA1-DIGEST of the clear password in the end.
*/
SET-SIZE(mBytes) = LENGTH(pcCreated) + 36. /* 16 + 20 = 36 */
/* Put the decoded nonce first */
PUT-BYTES(mBytes, 1) = mNonce.
/* Add create time */
PUT-STRING(mBytes, 17) = pcCreated. /* 16 + 1 = 17 */
/* Set SHA1 returns a 20 byte raw string. */
SET-SIZE(mSHA1) = 20.
mSHA1 = SHA1-DIGEST(pcPassword).
/* Add password, SHA1-digested (so we need to put bytes instead of a string */
PUT-BYTES(mBytes, 17 + LENGTH(pcCreated)) = mSHA1. /* 16 + 1 = 17 */
/* Create out-data in B64-encoded format */
pcHash = STRING(BASE64-ENCODE(SHA1-DIGEST(mBytes))).
/* Clean up mempointers */
SET-SIZE(mBytes) = 0.
SET-SIZE(mSHA1) = 0.
SET-SIZE(mNonce) = 0.
END PROCEDURE.
作为这个问题的跟进:Implementing WS-Security in Progress ABL,我将继续努力在 Progress OpenEdge 中实施 WS-Security。
我的问题:
在对特定 Web 服务的每个请求中,我都会根据以下内容生成密码摘要:
- A "nonce" - 随机字符串
- 一个时间戳 - 当前时间
- 密码 - 我和网络服务提供商之间的共享秘密。
然后将 nonce、时间戳和摘要添加到 Web 服务调用的 Soap header。
这在大多数情况下工作正常,但在 100 个请求中大约有 5 个失败(请参阅下面的更多信息)。
这是我生成摘要的方式:
PROCEDURE generatePassHashNonceClear:
/*------------------------------------------------------------------------------
Purpose:
Generates a password hash for WS-Security
General algorithm:
Digest = base64(sha1(Nonce + Timestamp + sha1(Pwd)))
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER pcNonce AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcCreated AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcPassword AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcHash AS CHARACTER NO-UNDO.
DEFINE VARIABLE mBytes AS MEMPTR NO-UNDO.
DEFINE VARIABLE mSHA1 AS MEMPTR NO-UNDO.
/*
Set size of mempointer, add 20 since we are adding the 20 byte
SHA1-DIGEST of the clear password in the end.
*/
SET-SIZE(mBytes) = LENGTH(pcNonce) + LENGTH(pcCreated) + 20.
/* Put the decoded nonce first */
PUT-STRING(mBytes, 1) = pcNonce.
/* Add create time */
PUT-STRING(mBytes, 1 + LENGTH(pcNonce)) = pcCreated.
/* Set SHA1 returns a 20 byte raw string. */
SET-SIZE(mSHA1) = 20.
mSHA1 = SHA1-DIGEST(pcPassword).
/* Add password, SHA1-digested (so we need to put bytes instead of a string */
PUT-BYTES(mBytes, 1 + LENGTH(pcNonce) + LENGTH(pcCreated)) = mSHA1.
/* Create out-data in B64-encoded format */
pcHash = STRING(BASE64-ENCODE(SHA1-DIGEST(mBytes))).
/* Clean up mempointers */
SET-SIZE(mBytes) = 0.
SET-SIZE(mSHA1) = 0.
END PROCEDURE.
程序是这样调用的:
DEFINE VARIABLE cPasswordClear AS CHARACTER NO-UNDO.
DEFINE VARIABLE dtZuluNow AS DATETIME NO-UNDO.
DEFINE VARIABLE cCreated AS CHARACTER NO-UNDO.
DEFINE VARIABLE cNonceB64 AS CHARACTER NO-UNDO.
DEFINE VARIABLE cNonce AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPasswordDigest AS CHARACTER NO-UNDO.
/*
Get time in UTC/GMT/ZULU/Timezone 0 and store
it with 000 as milliseconds + Z for timezone Zulu
Nonce is a random generated string
*/
ASSIGN
dtZuluNow = DATETIME-TZ(NOW,0)
cCreated = STRING(dtZuluNow, "9999-99-99THH:MM:SS") + ":000Z"
cPasswordClear = "SECRET"
cNonceB64 = BASE64-ENCODE(GENERATE-RANDOM-KEY)
cNonce = STRING(BASE64-DECODE(cNonceB64)).
RUN generatePassHashNonceClear( cNonce, cCreated, cPasswordClear, OUTPUT cPasswordDigest).
我知道的:
这在大约 10 000 个请求中的 9 500 个中工作正常。但是有5%的失败率。不幸的是,错误消息没有帮助,所以我真正能看到的是登录失败。 Web 服务提供商声明登录因不正确的摘要而被拒绝。
我做了什么:
为了测试我的摘要程序,我创建了一个小 python 程序。当我尝试使用失败登录的输入数据(nonce 和时间戳)时,这确实会创建不同的摘要。然而,我不是 Python 程序员,所以这个程序很可能有问题(但它也应该在相同的 95% 的情况下工作,这将是一个非常奇怪的巧合)。
这是 python 程序:
import hashlib
def createDigest(Nonce, Created, Password):
"This function returns a digest"
NonceB64 = Nonce.decode("base64","strict")
pdgst = hashlib.sha1()
pdgst.update(Password)
PasswordDgst = pdgst.digest()
FinalDgst = hashlib.sha1()
FinalDgst.update(NonceB64)
FinalDgst.update(Created)
FinalDgst.update(PasswordDgst)
FinalTxt = FinalDgst.digest().encode("base64","strict")
print "Final digest : " + FinalTxt
return
print "This digest is repeated in Progress OpenEdge"
createDigest("tGxF8+DAmJvQo93PNZt5Nw==", "2015-04-08T20:10:44:000Z", "SECRET")
print "This digest isn't repeated in Progress OpenEdge"
createDigest("XdcAW1TdTr+MLp4t0QkJ8g==", "2015-04-08T20:10:44:000Z", "SECRET")
我的真实密码当然不是"SECRET",这让我相信错误与随机数有关。将密码更改为 "SECRET" 会使摘要不同,但之后 Progress 和 Python 摘要之间的差异仍然存在(上面的第一个示例在更改前后生成了相似的摘要,但第二个没有)。
我有一个关于 Progress Support 的公开案例,但他们似乎和我一样为此苦苦挣扎。
我已经在 RHEL 和 Windows 7 上的 OpenEdge 11.3.1 和 11.4 中对此进行了测试,并且行为保持不变。
回答我自己的问题以供将来参考:
正如@TomBascom 指出的那样,问题与代码页转换有关,但实际错误确实在 "chain" 中早于 SHA 消化。
cNonceB64 = BASE64-ENCODE(GENERATE-RANDOM-KEY)
cNonce = STRING(BASE64-DECODE(cNonceB64))
在第二行中,只要生成的密钥包含 iso8859-1 和 UTF-8 之间不匹配的值,cNonce 的值就会被销毁。
简单的解决方案是将 cNonce 变量更改为内存指针,然后重写生成摘要的过程。
/* Optimistic, should really be based on current symmetric encryption algorithm */
SET-SIZE(mNonce) = 16.
ASSIGN
mNonce = GENERATE-RANDOM-KEY
cNonceB64 = BASE64-ENCODE(mNonce).
然后是生成密码摘要的新程序:
PROCEDURE generateDigest:
/*------------------------------------------------------------------------------
Purpose: Generates a password hash for WS-Security
Parameters: <none>
Notes:
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER mNonce AS MEMPTR NO-UNDO.
DEFINE INPUT PARAMETER pcCreated AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcPassword AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcHash AS CHARACTER NO-UNDO.
DEFINE VARIABLE mBytes AS MEMPTR NO-UNDO.
DEFINE VARIABLE mSHA1 AS MEMPTR NO-UNDO.
/*
Set size of mempointer, add 20 since we are adding the 20 byte
SHA1-DIGEST of the clear password in the end.
*/
SET-SIZE(mBytes) = LENGTH(pcCreated) + 36. /* 16 + 20 = 36 */
/* Put the decoded nonce first */
PUT-BYTES(mBytes, 1) = mNonce.
/* Add create time */
PUT-STRING(mBytes, 17) = pcCreated. /* 16 + 1 = 17 */
/* Set SHA1 returns a 20 byte raw string. */
SET-SIZE(mSHA1) = 20.
mSHA1 = SHA1-DIGEST(pcPassword).
/* Add password, SHA1-digested (so we need to put bytes instead of a string */
PUT-BYTES(mBytes, 17 + LENGTH(pcCreated)) = mSHA1. /* 16 + 1 = 17 */
/* Create out-data in B64-encoded format */
pcHash = STRING(BASE64-ENCODE(SHA1-DIGEST(mBytes))).
/* Clean up mempointers */
SET-SIZE(mBytes) = 0.
SET-SIZE(mSHA1) = 0.
SET-SIZE(mNonce) = 0.
END PROCEDURE.