001/* 002 * Copyright (c) 2010-2026 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) 062 .orElseThrow(() -> new NullPointerException("Parameter 'base64' cannot be null."))); 063 } 064 065 private static String padBase64(String base64) { 066 String padding = ""; 067 int remainder = base64.length() % 4; 068 069 if (remainder > 0) { 070 padding = IntStream.range(0, 4 - remainder).mapToObj(i -> "=").collect(Collectors.joining()); 071 } 072 073 return base64 + padding; 074 } 075 076 /** 077 * Encodes a hex {@code byte[]} from given {@code byte[]}. 078 * <p> 079 * This function is equivalent to Apache commons-codec binary {@code new Hex().encode(byte[])} 080 * 081 * @param data 082 * The data to encode as hex. 083 * @return Hex-encoded {@code byte[]} 084 * @throws NullPointerException 085 * If {@code data} is {@code null}. 086 */ 087 public static byte[] encodeHex(final byte[] data) { 088 Objects.requireNonNull(data, "Parameter 'data' cannot be null."); 089 char[] out = new char[data.length << 1]; 090 for (int j = 0; j < data.length; j++) { 091 int v = data[j] & 0xFF; 092 out[j * 2] = HEX_ARRAY[v >>> 4]; 093 out[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 094 } 095 return new String(out).getBytes(StandardCharsets.UTF_8); 096 } 097 098 /** 099 * Generates an appsecret_proof for facebook. 100 * <p> 101 * See <a href="https://developers.facebook.com/docs/graph-api/securing-requests"> 102 * https://developers.facebook.com/docs/graph-api/securing-requests</a> for more info 103 * 104 * @param appSecret 105 * The facebook application secret 106 * @param accessToken 107 * The facebook access token 108 * @return A Hex encoded SHA256 Hash as a String 109 */ 110 public static String encodeAppSecretProof(String appSecret, String accessToken) { 111 try { 112 byte[] key = appSecret.getBytes(StandardCharsets.UTF_8); 113 SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256"); 114 Mac mac = Mac.getInstance("HmacSHA256"); 115 mac.init(signingKey); 116 byte[] raw = mac.doFinal(accessToken.getBytes()); 117 byte[] hex = encodeHex(raw); 118 return new String(hex, StandardCharsets.UTF_8); 119 } catch (Exception e) { 120 throw new IllegalStateException("Creation of appsecret_proof has failed", e); 121 } 122 } 123}