Passed
Push — master ( 59bb35...8722e3 )
by Vincent
04:28
created

fr.arakne.utils.encoding.XorCipher.escape(String)   B

Complexity

Conditions 7

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.392

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
dl 0
loc 18
ccs 8
cts 10
cp 0.8
crap 7.392
rs 8
1
/*
2
 * This file is part of ArakneUtils.
3
 *
4
 * ArakneUtils is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * ArakneUtils is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with ArakneUtils.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2020 Vincent Quatrevieux
18
 */
19
20
package fr.arakne.utils.encoding;
21
22
import java.io.UnsupportedEncodingException;
23
import java.net.URLDecoder;
24
import java.net.URLEncoder;
25
import java.nio.charset.StandardCharsets;
26
27
/**
28
 * Implementation of the xor cipher in Dofus 1
29
 * Note: This cipher is not secure, the key can be easily retrieved.
30
 *
31
 * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L297
32
 */
33
final public class XorCipher {
34
    final private String key;
35
36 1
    public XorCipher(String key) {
37 1
        this.key = key;
38 1
    }
39
40
    /**
41
     * Get the key used by the cipher
42
     *
43
     * @return The key
44
     */
45
    public String key() {
46 1
        return key;
47
    }
48
49
    /**
50
     * Encrypt the value using using the current key
51
     * The encrypted value is an upper case hexadecimal string
52
     *
53
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L297
54
     *
55
     * @param value Value to encrypt
56
     * @param keyOffset Offset to use for the key
57
     *
58
     * @return Encrypted value
59
     */
60
    public String encrypt(String value, int keyOffset) {
61 1
        value = escape(value);
62 1
        StringBuilder encrypted = new StringBuilder(value.length() * 2);
63
64 1
        for (int i = 0; i < value.length(); ++i) {
65 1
            final char c = value.charAt(i);
66 1
            final char k = key.charAt((i + keyOffset) % key.length());
67
68 1
            final char e = (char) (c ^ k);
69
70 1
            if (e < 16) {
71 1
                encrypted.append('0');
72
            }
73
74 1
            encrypted.append(Integer.toHexString(e));
75
        }
76
77 1
        return encrypted.toString().toUpperCase();
78
    }
79
80
    /**
81
     * Decrypt the value using the current key
82
     *
83
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L314
84
     *
85
     * @param value Value to decrypt. Must be a valid hexadecimal string
86
     * @param keyOffset Offset to use on the key. Must be the same used for encryption.
87
     *
88
     * @return The decrypted value
89
     *
90
     * @throws IllegalArgumentException When an invalid string is given
91
     * @throws NumberFormatException When an invalid hexadecimal string is given
92
     */
93
    public String decrypt(String value, int keyOffset) {
94 1
        if (value.length() % 2 != 0) {
95 1
            throw new IllegalArgumentException("Invalid encrypted value");
96
        }
97
98 1
        char[] decrypted = new char[value.length() / 2];
99
100 1
        for (int i = 0; i < value.length(); i += 2) {
101 1
            final char k = key.charAt((i / 2 + keyOffset) % key.length());
102 1
            final char c = (char) Integer.parseInt(value.substring(i, i + 2), 16);
103
104 1
            decrypted[i / 2] = (char) (c ^ k);
105
        }
106
107
        try {
108 1
            return URLDecoder.decode(new String(decrypted), StandardCharsets.UTF_8.toString());
109
        } catch (UnsupportedEncodingException e) {
110
            throw new IllegalArgumentException("Invalid UTF-8 character", e);
111
        }
112
    }
113
114
    /**
115
     * Escape value using URL encode
116
     *
117
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L275
118
     */
119
    static private String escape(String value) {
120 1
        StringBuilder escaped = new StringBuilder(value.length());
121
122
        try {
123 1
            for (int i = 0; i < value.length(); ++i) {
124 1
                final char c = value.charAt(i);
125
126 1
                if (c < 32 || c > 127 || c == '%' || c == '+') {
127 1
                    escaped.append(URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8.toString()));
128
                } else {
129 1
                    escaped.append(c);
130
                }
131
            }
132
        } catch (UnsupportedEncodingException e) {
133
            throw new IllegalArgumentException("Invalid UTF-8 character", e);
134 1
        }
135
136 1
        return escaped.toString();
137
    }
138
}
139