Created
April 5, 2015 03:48
-
-
Save cab404/f1f35fa0ef1c24eee0a8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.cab404; | |
import java.nio.charset.Charset; | |
import java.security.MessageDigest; | |
import java.security.NoSuchAlgorithmException; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.concurrent.atomic.AtomicInteger; | |
/** | |
* Partially implements 'Digest access authentication', as described in RFC 2617 | |
* <p/> | |
* Only feature I have not implemented is 'MD5-sess' algorithm, but I can add it on your demand. | |
* <p/> | |
* Contact me via e-mail: me@cab404.ru | |
* <p/> | |
* Created at 16:50 on 31.03.15 | |
* | |
* @author cab404 | |
*/ | |
public class DigestAuthGenerator { | |
AtomicInteger nonce_count = new AtomicInteger(0); | |
// public static void main(String[] args) { | |
// System.out.println( | |
// new DigestAuthGenerator() | |
// .genAuthenticationHeader( | |
// "GET", | |
// "/", | |
// "Digest realm=\"ZyXEL Keenetic Start\", nonce=\"14143d\", qop=\"auth\"", | |
// "asdf", | |
// "dsfa" | |
// ) | |
// | |
// ); | |
// } | |
/** | |
* Generates Authentication header from WWW-Authenticate | |
* | |
* @param request_method HTTP method you used to get WWW-Authenticate | |
* @param uri Server URI you used to get WWW-Authenticate | |
* @param www_authenticate WWW-Authenticate header value | |
* @param login Your login | |
* @param pwd Your password | |
*/ | |
public String genAuthenticationHeader(String request_method, String uri, String www_authenticate, String login, String pwd) { | |
Map<String, String> auth_data = parseAuth(www_authenticate); | |
Map<String, String> ret_data = new HashMap<>(); | |
ret_data.put("realm", auth_data.get("Digest realm")); | |
ret_data.put("nonce", auth_data.get("nonce")); | |
ret_data.put("uri", '\"' + uri + '\"'); | |
ret_data.put("Digest username", '\"' + login + '\"'); | |
String qop = auth_data.get("qop"); | |
String algorithm = auth_data.get("algorithm"); | |
String nonce = auth_data.get("nonce"); | |
String realm = auth_data.get("Digest realm"); | |
String nc = fillZeroes(8, nonce_count.incrementAndGet()); | |
if (realm != null) | |
realm = trim(realm, '"').toString(); | |
else | |
throw new RuntimeException("No realm specified."); | |
if (nonce != null) | |
nonce = trim(nonce, '"').toString(); | |
else | |
throw new RuntimeException("No nonce specified."); | |
String a1, a2, response; | |
a1 = md5(login + ':' + realm + ':' + pwd); | |
a2 = md5(request_method + ':' + uri); | |
if (qop != null) { | |
qop = trim(qop, '"').toString(); | |
String cnonce = md5(Math.random() + "").substring(16); | |
ret_data.put("cnonce", cnonce); | |
if (algorithm != null) | |
ret_data.put("algorithm", auth_data.get("algorithm")); | |
if ("MD5-sess".equalsIgnoreCase(algorithm)) | |
a1 = md5(a1 + ':' + nonce + ':' + cnonce); | |
if ("auth-int".equalsIgnoreCase(qop)) | |
throw new UnsupportedOperationException("We can't auth-int without body"); | |
response = md5(a1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + a2); | |
ret_data.put("cnonce", '"' + cnonce + '"'); | |
ret_data.put("qop", qop); | |
ret_data.put("nc", nc); | |
} else | |
response = md5(a1 + ':' + nonce + ':' + a2); | |
ret_data.put("response", '"' + response + '"'); | |
return buildAuth(ret_data); | |
} | |
private static String fillZeroes(int to, int what) { | |
StringBuilder builder = new StringBuilder(what + ""); | |
while (builder.length() < to) builder.insert(0, '0'); | |
return builder.toString(); | |
} | |
private static String md5(String str) { | |
try { | |
MessageDigest digest; | |
digest = MessageDigest.getInstance("MD5"); | |
digest.update(str.getBytes(Charset.forName("UTF-8"))); | |
StringBuilder hash = new StringBuilder(); | |
for (byte b : digest.digest()) { | |
String num = Integer.toString(b & 255, 16); | |
if (num.length() == 1) | |
hash.append('0'); | |
hash.append(num); | |
} | |
return hash.toString(); | |
} catch (NoSuchAlgorithmException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
/** | |
* Parses auth header to map | |
*/ | |
private static Map<String, String> parseAuth(String auth) { | |
HashMap<String, String> data = new HashMap<>(); | |
for (String entry : splitToArray(auth, auth.length(), ',')) { | |
CharSequence[] entry_split = splitToArray(entry, 2, '='); | |
entry_split[0] = trim(entry_split[0], ' '); | |
entry_split[1] = trim(entry_split[1], ' '); | |
data.put( | |
entry_split[0].toString(), | |
entry_split[1].toString() | |
); | |
} | |
return data; | |
} | |
/** | |
* Makes auth header out of map | |
*/ | |
private static String buildAuth(Map<String, String> data) { | |
StringBuilder builder = new StringBuilder(); | |
for (Map.Entry<String, String> e : data.entrySet()) | |
builder.append(e.getKey()).append("=").append(e.getValue()).append("").append(", "); | |
if (builder.length() > 0) | |
builder.delete(builder.length() - 2, builder.length()); | |
return builder.toString(); | |
} | |
private static int count(CharSequence seq, char ch) { | |
int counter = 0; | |
for (int i = 0; i < seq.length(); i++) | |
if (seq.charAt(i) == ch) | |
counter++; | |
return counter; | |
} | |
/** | |
* Splits string using ch | |
*/ | |
private static String[] splitToArray(String source, int limit, char ch) { | |
int occurences = 0; | |
occurences += count(source, ch); | |
String[] out = new String[(occurences + 1) > limit ? limit : (occurences + 1)]; | |
int last = 0; | |
int array_index = 0; | |
for (int i = 0; i < source.length(); i++) { | |
if (ch == source.charAt(i)) { | |
out[array_index++] = source.substring(last, i); | |
last = i + 1; | |
if (array_index + 1 == limit) | |
break; | |
} | |
} | |
out[array_index] = source.substring(last); | |
return out; | |
} | |
/** | |
* Trims given chars in a CharSequence | |
*/ | |
private static CharSequence trim(CharSequence seq, char c) { | |
if (seq.length() == 0) return seq; | |
int start = 0; | |
int end = seq.length() - 1; | |
for (; start < seq.length(); start++) if (seq.charAt(start) != c) break; | |
for (; end >= start; end--) if (seq.charAt(end) != c) break; | |
return seq.subSequence(start, end + 1); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment