NTLM 是 Windows 的一種登入方式,它會要求使用者打入帳號密碼登入,
此時瀏覽器會跳出一個登入的對話框,當使用者登入後,
可以經由解析Authorization得到使用者的username和domain,
以下是參考這篇文
NTLM Authentication in Java
寫出的 Java 程式碼,可以得到username和domain,
要注意的是,在
NTLM Authentication in Java 的程式碼中,其忽略了 username 和 doamin 的 length 或 offset, byte 轉成 int 時會變成負數的可能性,
而在我們的程式碼中對此做出了修正。
String auth = request.getHeader("Authorization");
String szUserName = "";
String szDomain = "";
StringBuffer szUserNameBuffer = new StringBuffer();
StringBuffer szDomainBuffer = new StringBuffer();
if (auth == null)
{
response.setStatus(response.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "NTLM");
response.flushBuffer();
return;
}
if (auth.startsWith("NTLM "))
{
byte[] msg = new sun.misc.BASE64Decoder().decodeBuffer(auth.substring(5));
int off = 0, length, offset;
if (msg[8] == 1)
{
byte z = 0;
byte[] msg1 = {(byte)'N', (byte)'T', (byte)'L', (byte)'M', (byte)'S', (byte)'S', (byte)'P', z,(byte)2, z, z, z, z, z, z, z,(byte)40, z, z, z, (byte)1, (byte)130, z, z,z, (byte)2, (byte)2, (byte)2, z, z, z, z, z, z, z, z, z, z, z, z};
response.setHeader("WWW-Authenticate", "NTLM " + new sun.misc.BASE64Encoder().encodeBuffer(msg1));
response.sendError(response.SC_UNAUTHORIZED);
return;
}
else if (msg[8] == 3)
{
off = 30;
length = (msg[off+17]&0x0FF)*256 + (msg[off+16]&0x0FF);
offset = (msg[off+19]&0x0FF)*256 + (msg[off+18]&0x0FF);
String remoteHost = new String(msg, offset, length);
length = (msg[off+1]&0x0FF)*256 + (msg[off]&0x0FF);
offset = (msg[off+3]&0x0FF)*256 + (msg[off+2]&0x0FF);
String domain = new String(msg, offset, length);
length = (msg[off+9]&0x0FF)*256 + (msg[off+8]&0x0FF);
offset = (msg[off+11]&0x0FF)*256 + (msg[off+10]&0x0FF);
String username = new String(msg, offset, length);
char[] ca = remoteHost.toCharArray();
String szHost = "";
for(int i = 0; i < ca.length; i = i + 2){
szHost = szHost + (char)ca[i];
}
char[] ca2 = domain.toCharArray();
for(int i = 0; i < ca2.length; i = i + 2){
szDomain = szDomain + (char)ca2[i];
}
char[] ca3 = username.toCharArray();
for(int i = 0; i < ca3.length; i = i + 2){
szUserName = szUserName + (char)ca3[i];
}
}
}else if(auth.startsWith("Negotiate")){
byte[] msg = new sun.misc.BASE64Decoder().decodeBuffer(auth.substring(10));
int off = 0, length, offset;
if (msg[8] == 1){
byte z = 0;
byte[] msg1 = {(byte)'N', (byte)'T', (byte)'L', (byte)'M', (byte)'S', (byte)'S', (byte)'P',
z,(byte)2, z, z, z, z, z, z, z,(byte)40, z, z, z,
(byte)1, (byte)130, z, z,z, (byte)2, (byte)2,
(byte)2, z, z, z, z, z, z, z, z, z, z, z, z};
response.setHeader("WWW-Authenticate", "NTLM " + new sun.misc.BASE64Encoder().encodeBuffer(msg1));
response.sendError(response.SC_UNAUTHORIZED);
return;
}else if (msg[8] == 3){
off = 30;
length = (msg[off+17]&0x0FF)*256 + (msg[off+16]&0x0FF);
offset = (msg[off+19]&0x0FF)*256 + (msg[off+18]&0x0FF);
// String remoteHost = new String(msg, offset, length);
length = (msg[off+1]&0x0FF)*256 + (msg[off]&0x0FF);
offset = (msg[off+3]&0x0FF)*256 + (msg[off+2]&0x0FF);
szDomain = new String(msg, offset, length);
length = (msg[off+9]&0x0FF)*256 + (msg[off+8]&0x0FF);
offset = (msg[off+11]&0x0FF)*256 + (msg[off+10]&0x0FF);
szUserName = new String(msg, offset, length , "UTF-8" );
}
}
String charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_".toLowerCase();
for(int i=0; i<szUserName.length();i++){
if(charSet.contains(String.valueOf(szUserName.charAt(i)).toLowerCase())){
szUserNameBuffer.append(szUserName.charAt(i));
}
}
for(int i=0; i<szDomain.length();i++){
if(charSet.contains(String.valueOf(szDomain.charAt(i)).toLowerCase())){
szDomainBuffer.append(szDomain.charAt(i));
}
}
修正的方式很簡單,就是
將byte與 0x0FF 進行和(&) 運算,
那麼為什麼會需要這樣的修正呢?
我們可以看看 NTLM Header 的 Schema,在
NTLM Authentication Scheme for HTT 中
有說明到
"
byte is an 8-bit field; short is a 16-bit field. All fields are unsigned. Numbers are stored in little-endian order. "
以domain length 為例,表示了其是用 short 16-bit 來儲存,
並且是用 little-endian order,即後8-bit 為高位,
最後為unsigned的,也就是一定為正數,即使8-bit中的最高位為1也是一樣為正。
如果在 8-bit 中的最高位為0, byte 轉 int 是沒問題的,
但是如果最高位為1的話,直接轉就會出問題了,
例如: 144 (NTLM- unsigned 10進位)
1001 0000 (byte) 在轉成 int 時,因為 Java 看到最高位為1,誤以為是負數,且 int 為 32-bit,所以就在前面多出的24個位元補上了1,而變成
1111 1111 1111 1111 1111 1111 1001 0000 (int) ==> -112 (Java signed 10 進位)
這樣就出錯了,
很顯然,前面的24個位元應該都要為0才對,所以我們只要在對其與 0xFF進行和(&)運算,
將前面24個位元都變成0就行了。
0000 0000 0000 0000 0000 0000 1001 0000 (int) ==> 144 (NTLD unsigned 10 進位)
參考資料:
- NTLM Authentication Scheme for HTT
- NTLM Authentication in Java (此處程式碼不夠好,沒有考慮到本文錯誤取到負值的狀況)
- java中byte轉換int時為何與0xff進行與運算
- Base64