001/* 002 * Copyright (c) 2010-2024 Mark Allen, Norbert Bartels. 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 020 * THE SOFTWARE. 021 */ 022package com.restfb.util; 023 024import java.nio.charset.StandardCharsets; 025import java.util.Base64; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.stream.Collectors; 029import java.util.stream.IntStream; 030 031import javax.crypto.Mac; 032import javax.crypto.spec.SecretKeySpec; 033 034/** 035 * A collection of data-encoding utility methods. 036 * 037 * @author Josef Gierbl 038 * @author Mikael Grev 039 * @author <a href="http://restfb.com">Mark Allen</a> 040 * @since 1.6.13 041 */ 042public final class EncodingUtils { 043 044 /** 045 * Prevents instantiation. 046 */ 047 private EncodingUtils() {} 048 049 private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); 050 051 /** 052 * Decodes a base64-encoded string, padding out if necessary. 053 * 054 * @param base64 055 * The base64-encoded string to decode. 056 * @return A decoded version of {@code base64}. 057 * @throws NullPointerException 058 * If {@code base64} is {@code null}. 059 */ 060 public static byte[] decodeBase64(String base64) { 061 return Base64.getDecoder().decode(Optional.ofNullable(base64).map(EncodingUtils::padBase64).orElseThrow(() -> new NullPointerException("Parameter 'base64' cannot be null."))); 062 } 063 064 private static String padBase64(String base64) { 065 String padding = ""; 066 int remainder = base64.length() % 4; 067 068 if (remainder > 0) { 069 padding = IntStream.range(0, 4 - remainder).mapToObj(i -> "=").collect(Collectors.joining()); 070 } 071 072 return base64 + padding; 073 } 074 075 /** 076 * Encodes a hex {@code byte[]} from given {@code byte[]}. 077 * <p> 078 * This function is equivalent to Apache commons-codec binary {@code new Hex().encode(byte[])} 079 * 080 * @param data 081 * The data to encode as hex. 082 * @return Hex-encoded {@code byte[]} 083 * @throws NullPointerException 084 * If {@code data} is {@code null}. 085 */ 086 public static byte[] encodeHex(final byte[] data) { 087 Objects.requireNonNull(data, "Parameter 'data' cannot be null."); 088 char[] out = new char[data.length << 1]; 089 for (int j = 0; j < data.length; j++) { 090 int v = data[j] & 0xFF; 091 out[j * 2] = HEX_ARRAY[v >>> 4]; 092 out[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 093 } 094 return new String(out).getBytes(StandardCharsets.UTF_8); 095 } 096 097 /** 098 * Generates an appsecret_proof for facebook. 099 * <p> 100 * See https://developers.facebook.com/docs/graph-api/securing-requests for more info 101 * 102 * @param appSecret 103 * The facebook application secret 104 * @param accessToken 105 * The facebook access token 106 * @return A Hex encoded SHA256 Hash as a String 107 */ 108 public static String encodeAppSecretProof(String appSecret, String accessToken) { 109 try { 110 byte[] key = appSecret.getBytes(StandardCharsets.UTF_8); 111 SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256"); 112 Mac mac = Mac.getInstance("HmacSHA256"); 113 mac.init(signingKey); 114 byte[] raw = mac.doFinal(accessToken.getBytes()); 115 byte[] hex = encodeHex(raw); 116 return new String(hex, StandardCharsets.UTF_8); 117 } catch (Exception e) { 118 throw new IllegalStateException("Creation of appsecret_proof has failed", e); 119 } 120 } 121}