Skip to content

Instantly share code, notes, and snippets.

@cab404
Created April 5, 2015 03:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cab404/f1f35fa0ef1c24eee0a8 to your computer and use it in GitHub Desktop.
Save cab404/f1f35fa0ef1c24eee0a8 to your computer and use it in GitHub Desktop.
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