KeyProperties.java revision 3f8d4d840894468f2be8a5b56ff266cef2d71c50
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.security.keystore; 18 19import android.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.annotation.StringDef; 23import android.security.keymaster.KeymasterDefs; 24 25import libcore.util.EmptyArray; 26 27import java.lang.annotation.Retention; 28import java.lang.annotation.RetentionPolicy; 29import java.util.Collection; 30import java.util.Locale; 31 32/** 33 * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys. 34 */ 35public abstract class KeyProperties { 36 private KeyProperties() {} 37 38 /** 39 * @hide 40 */ 41 @Retention(RetentionPolicy.SOURCE) 42 @IntDef(flag = true, 43 value = { 44 PURPOSE_ENCRYPT, 45 PURPOSE_DECRYPT, 46 PURPOSE_SIGN, 47 PURPOSE_VERIFY, 48 }) 49 public @interface PurposeEnum {} 50 51 /** 52 * Purpose of key: encryption. 53 */ 54 public static final int PURPOSE_ENCRYPT = 1 << 0; 55 56 /** 57 * Purpose of key: decryption. 58 */ 59 public static final int PURPOSE_DECRYPT = 1 << 1; 60 61 /** 62 * Purpose of key: signing or generating a Message Authentication Code (MAC). 63 */ 64 public static final int PURPOSE_SIGN = 1 << 2; 65 66 /** 67 * Purpose of key: signature or Message Authentication Code (MAC) verification. 68 */ 69 public static final int PURPOSE_VERIFY = 1 << 3; 70 71 /** 72 * @hide 73 */ 74 public static abstract class Purpose { 75 private Purpose() {} 76 77 public static int toKeymaster(@PurposeEnum int purpose) { 78 switch (purpose) { 79 case PURPOSE_ENCRYPT: 80 return KeymasterDefs.KM_PURPOSE_ENCRYPT; 81 case PURPOSE_DECRYPT: 82 return KeymasterDefs.KM_PURPOSE_DECRYPT; 83 case PURPOSE_SIGN: 84 return KeymasterDefs.KM_PURPOSE_SIGN; 85 case PURPOSE_VERIFY: 86 return KeymasterDefs.KM_PURPOSE_VERIFY; 87 default: 88 throw new IllegalArgumentException("Unknown purpose: " + purpose); 89 } 90 } 91 92 public static @PurposeEnum int fromKeymaster(int purpose) { 93 switch (purpose) { 94 case KeymasterDefs.KM_PURPOSE_ENCRYPT: 95 return PURPOSE_ENCRYPT; 96 case KeymasterDefs.KM_PURPOSE_DECRYPT: 97 return PURPOSE_DECRYPT; 98 case KeymasterDefs.KM_PURPOSE_SIGN: 99 return PURPOSE_SIGN; 100 case KeymasterDefs.KM_PURPOSE_VERIFY: 101 return PURPOSE_VERIFY; 102 default: 103 throw new IllegalArgumentException("Unknown purpose: " + purpose); 104 } 105 } 106 107 @NonNull 108 public static int[] allToKeymaster(@PurposeEnum int purposes) { 109 int[] result = getSetFlags(purposes); 110 for (int i = 0; i < result.length; i++) { 111 result[i] = toKeymaster(result[i]); 112 } 113 return result; 114 } 115 116 public static @PurposeEnum int allFromKeymaster(@NonNull Collection<Integer> purposes) { 117 @PurposeEnum int result = 0; 118 for (int keymasterPurpose : purposes) { 119 result |= fromKeymaster(keymasterPurpose); 120 } 121 return result; 122 } 123 } 124 125 /** 126 * @hide 127 */ 128 @Retention(RetentionPolicy.SOURCE) 129 @StringDef({ 130 KEY_ALGORITHM_RSA, 131 KEY_ALGORITHM_EC, 132 KEY_ALGORITHM_AES, 133 KEY_ALGORITHM_HMAC_SHA1, 134 KEY_ALGORITHM_HMAC_SHA224, 135 KEY_ALGORITHM_HMAC_SHA256, 136 KEY_ALGORITHM_HMAC_SHA384, 137 KEY_ALGORITHM_HMAC_SHA512, 138 }) 139 public @interface KeyAlgorithmEnum {} 140 141 /** Rivest Shamir Adleman (RSA) key. */ 142 public static final String KEY_ALGORITHM_RSA = "RSA"; 143 144 /** Elliptic Curve (EC) Cryptography key. */ 145 public static final String KEY_ALGORITHM_EC = "EC"; 146 147 /** Advanced Encryption Standard (AES) key. */ 148 public static final String KEY_ALGORITHM_AES = "AES"; 149 150 /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ 151 public static final String KEY_ALGORITHM_HMAC_SHA1 = "HmacSHA1"; 152 153 /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ 154 public static final String KEY_ALGORITHM_HMAC_SHA224 = "HmacSHA224"; 155 156 /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ 157 public static final String KEY_ALGORITHM_HMAC_SHA256 = "HmacSHA256"; 158 159 /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ 160 public static final String KEY_ALGORITHM_HMAC_SHA384 = "HmacSHA384"; 161 162 /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ 163 public static final String KEY_ALGORITHM_HMAC_SHA512 = "HmacSHA512"; 164 165 /** 166 * @hide 167 */ 168 public static abstract class KeyAlgorithm { 169 private KeyAlgorithm() {} 170 171 public static int toKeymasterSecretKeyAlgorithm( 172 @NonNull @KeyAlgorithmEnum String algorithm) { 173 if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) { 174 return KeymasterDefs.KM_ALGORITHM_AES; 175 } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { 176 return KeymasterDefs.KM_ALGORITHM_HMAC; 177 } else { 178 throw new IllegalArgumentException( 179 "Unsupported secret key algorithm: " + algorithm); 180 } 181 } 182 183 @NonNull 184 public static @KeyAlgorithmEnum String fromKeymasterSecretKeyAlgorithm( 185 int keymasterAlgorithm, int keymasterDigest) { 186 switch (keymasterAlgorithm) { 187 case KeymasterDefs.KM_ALGORITHM_AES: 188 if (keymasterDigest != -1) { 189 throw new IllegalArgumentException("Digest not supported for AES key: " 190 + Digest.fromKeymaster(keymasterDigest)); 191 } 192 return KEY_ALGORITHM_AES; 193 case KeymasterDefs.KM_ALGORITHM_HMAC: 194 switch (keymasterDigest) { 195 case KeymasterDefs.KM_DIGEST_SHA1: 196 return KEY_ALGORITHM_HMAC_SHA1; 197 case KeymasterDefs.KM_DIGEST_SHA_2_224: 198 return KEY_ALGORITHM_HMAC_SHA224; 199 case KeymasterDefs.KM_DIGEST_SHA_2_256: 200 return KEY_ALGORITHM_HMAC_SHA256; 201 case KeymasterDefs.KM_DIGEST_SHA_2_384: 202 return KEY_ALGORITHM_HMAC_SHA384; 203 case KeymasterDefs.KM_DIGEST_SHA_2_512: 204 return KEY_ALGORITHM_HMAC_SHA512; 205 default: 206 throw new IllegalArgumentException("Unsupported HMAC digest: " 207 + Digest.fromKeymaster(keymasterDigest)); 208 } 209 default: 210 throw new IllegalArgumentException( 211 "Unsupported key algorithm: " + keymasterAlgorithm); 212 } 213 } 214 215 /** 216 * @hide 217 * 218 * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. 219 */ 220 public static int toKeymasterDigest(@NonNull @KeyAlgorithmEnum String algorithm) { 221 String algorithmUpper = algorithm.toUpperCase(Locale.US); 222 if (algorithmUpper.startsWith("HMAC")) { 223 String digestUpper = algorithmUpper.substring("HMAC".length()); 224 switch (digestUpper) { 225 case "SHA1": 226 return KeymasterDefs.KM_DIGEST_SHA1; 227 case "SHA224": 228 return KeymasterDefs.KM_DIGEST_SHA_2_224; 229 case "SHA256": 230 return KeymasterDefs.KM_DIGEST_SHA_2_256; 231 case "SHA384": 232 return KeymasterDefs.KM_DIGEST_SHA_2_384; 233 case "SHA512": 234 return KeymasterDefs.KM_DIGEST_SHA_2_512; 235 default: 236 throw new IllegalArgumentException( 237 "Unsupported HMAC digest: " + digestUpper); 238 } 239 } else { 240 return -1; 241 } 242 } 243 } 244 245 /** 246 * @hide 247 */ 248 @Retention(RetentionPolicy.SOURCE) 249 @StringDef({ 250 BLOCK_MODE_ECB, 251 BLOCK_MODE_CBC, 252 BLOCK_MODE_CTR, 253 BLOCK_MODE_GCM, 254 }) 255 public @interface BlockModeEnum {} 256 257 /** Electronic Codebook (ECB) block mode. */ 258 public static final String BLOCK_MODE_ECB = "ECB"; 259 260 /** Cipher Block Chaining (CBC) block mode. */ 261 public static final String BLOCK_MODE_CBC = "CBC"; 262 263 /** Counter (CTR) block mode. */ 264 public static final String BLOCK_MODE_CTR = "CTR"; 265 266 /** Galois/Counter Mode (GCM) block mode. */ 267 public static final String BLOCK_MODE_GCM = "GCM"; 268 269 /** 270 * @hide 271 */ 272 public static abstract class BlockMode { 273 private BlockMode() {} 274 275 public static int toKeymaster(@NonNull @BlockModeEnum String blockMode) { 276 if (BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) { 277 return KeymasterDefs.KM_MODE_ECB; 278 } else if (BLOCK_MODE_CBC.equalsIgnoreCase(blockMode)) { 279 return KeymasterDefs.KM_MODE_CBC; 280 } else if (BLOCK_MODE_CTR.equalsIgnoreCase(blockMode)) { 281 return KeymasterDefs.KM_MODE_CTR; 282 } else if (BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) { 283 return KeymasterDefs.KM_MODE_GCM; 284 } else { 285 throw new IllegalArgumentException("Unsupported block mode: " + blockMode); 286 } 287 } 288 289 @NonNull 290 public static @BlockModeEnum String fromKeymaster(int blockMode) { 291 switch (blockMode) { 292 case KeymasterDefs.KM_MODE_ECB: 293 return BLOCK_MODE_ECB; 294 case KeymasterDefs.KM_MODE_CBC: 295 return BLOCK_MODE_CBC; 296 case KeymasterDefs.KM_MODE_CTR: 297 return BLOCK_MODE_CTR; 298 case KeymasterDefs.KM_MODE_GCM: 299 return BLOCK_MODE_GCM; 300 default: 301 throw new IllegalArgumentException("Unsupported block mode: " + blockMode); 302 } 303 } 304 305 @NonNull 306 public static @BlockModeEnum String[] allFromKeymaster( 307 @NonNull Collection<Integer> blockModes) { 308 if ((blockModes == null) || (blockModes.isEmpty())) { 309 return EmptyArray.STRING; 310 } 311 @BlockModeEnum String[] result = new String[blockModes.size()]; 312 int offset = 0; 313 for (int blockMode : blockModes) { 314 result[offset] = fromKeymaster(blockMode); 315 offset++; 316 } 317 return result; 318 } 319 320 public static int[] allToKeymaster(@Nullable @BlockModeEnum String[] blockModes) { 321 if ((blockModes == null) || (blockModes.length == 0)) { 322 return EmptyArray.INT; 323 } 324 int[] result = new int[blockModes.length]; 325 for (int i = 0; i < blockModes.length; i++) { 326 result[i] = toKeymaster(blockModes[i]); 327 } 328 return result; 329 } 330 } 331 332 /** 333 * @hide 334 */ 335 @Retention(RetentionPolicy.SOURCE) 336 @StringDef({ 337 ENCRYPTION_PADDING_NONE, 338 ENCRYPTION_PADDING_PKCS7, 339 ENCRYPTION_PADDING_RSA_PKCS1, 340 ENCRYPTION_PADDING_RSA_OAEP, 341 }) 342 public @interface EncryptionPaddingEnum {} 343 344 /** 345 * No encryption padding. 346 */ 347 public static final String ENCRYPTION_PADDING_NONE = "NoPadding"; 348 349 /** 350 * PKCS#7 encryption padding scheme. 351 */ 352 public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding"; 353 354 /** 355 * RSA PKCS#1 v1.5 padding scheme for encryption. 356 */ 357 public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding"; 358 359 /** 360 * RSA Optimal Asymmetric Encryption Padding (OAEP) scheme. 361 */ 362 public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding"; 363 364 /** 365 * @hide 366 */ 367 public static abstract class EncryptionPadding { 368 private EncryptionPadding() {} 369 370 public static int toKeymaster(@NonNull @EncryptionPaddingEnum String padding) { 371 if (ENCRYPTION_PADDING_NONE.equalsIgnoreCase(padding)) { 372 return KeymasterDefs.KM_PAD_NONE; 373 } else if (ENCRYPTION_PADDING_PKCS7.equalsIgnoreCase(padding)) { 374 return KeymasterDefs.KM_PAD_PKCS7; 375 } else if (ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(padding)) { 376 return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; 377 } else if (ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(padding)) { 378 return KeymasterDefs.KM_PAD_RSA_OAEP; 379 } else { 380 throw new IllegalArgumentException( 381 "Unsupported encryption padding scheme: " + padding); 382 } 383 } 384 385 @NonNull 386 public static @EncryptionPaddingEnum String fromKeymaster(int padding) { 387 switch (padding) { 388 case KeymasterDefs.KM_PAD_NONE: 389 return ENCRYPTION_PADDING_NONE; 390 case KeymasterDefs.KM_PAD_PKCS7: 391 return ENCRYPTION_PADDING_PKCS7; 392 case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: 393 return ENCRYPTION_PADDING_RSA_PKCS1; 394 case KeymasterDefs.KM_PAD_RSA_OAEP: 395 return ENCRYPTION_PADDING_RSA_OAEP; 396 default: 397 throw new IllegalArgumentException( 398 "Unsupported encryption padding: " + padding); 399 } 400 } 401 402 @NonNull 403 public static int[] allToKeymaster(@Nullable @EncryptionPaddingEnum String[] paddings) { 404 if ((paddings == null) || (paddings.length == 0)) { 405 return EmptyArray.INT; 406 } 407 int[] result = new int[paddings.length]; 408 for (int i = 0; i < paddings.length; i++) { 409 result[i] = toKeymaster(paddings[i]); 410 } 411 return result; 412 } 413 } 414 415 /** 416 * @hide 417 */ 418 @Retention(RetentionPolicy.SOURCE) 419 @StringDef({ 420 SIGNATURE_PADDING_RSA_PKCS1, 421 SIGNATURE_PADDING_RSA_PSS, 422 }) 423 public @interface SignaturePaddingEnum {} 424 425 /** 426 * RSA PKCS#1 v1.5 padding for signatures. 427 */ 428 public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1"; 429 430 /** 431 * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. 432 */ 433 public static final String SIGNATURE_PADDING_RSA_PSS = "PSS"; 434 435 static abstract class SignaturePadding { 436 private SignaturePadding() {} 437 438 static int toKeymaster(@NonNull @SignaturePaddingEnum String padding) { 439 switch (padding.toUpperCase(Locale.US)) { 440 case SIGNATURE_PADDING_RSA_PKCS1: 441 return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; 442 case SIGNATURE_PADDING_RSA_PSS: 443 return KeymasterDefs.KM_PAD_RSA_PSS; 444 default: 445 throw new IllegalArgumentException( 446 "Unsupported signature padding scheme: " + padding); 447 } 448 } 449 450 @NonNull 451 static @SignaturePaddingEnum String fromKeymaster(int padding) { 452 switch (padding) { 453 case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: 454 return SIGNATURE_PADDING_RSA_PKCS1; 455 case KeymasterDefs.KM_PAD_RSA_PSS: 456 return SIGNATURE_PADDING_RSA_PSS; 457 default: 458 throw new IllegalArgumentException("Unsupported signature padding: " + padding); 459 } 460 } 461 462 @NonNull 463 static int[] allToKeymaster(@Nullable @SignaturePaddingEnum String[] paddings) { 464 if ((paddings == null) || (paddings.length == 0)) { 465 return EmptyArray.INT; 466 } 467 int[] result = new int[paddings.length]; 468 for (int i = 0; i < paddings.length; i++) { 469 result[i] = toKeymaster(paddings[i]); 470 } 471 return result; 472 } 473 } 474 475 /** 476 * @hide 477 */ 478 @Retention(RetentionPolicy.SOURCE) 479 @StringDef({ 480 DIGEST_NONE, 481 DIGEST_MD5, 482 DIGEST_SHA1, 483 DIGEST_SHA224, 484 DIGEST_SHA256, 485 DIGEST_SHA384, 486 DIGEST_SHA512, 487 }) 488 public @interface DigestEnum {} 489 490 /** 491 * No digest: sign/authenticate the raw message. 492 */ 493 public static final String DIGEST_NONE = "NONE"; 494 495 /** 496 * MD5 digest. 497 */ 498 public static final String DIGEST_MD5 = "MD5"; 499 500 /** 501 * SHA-1 digest. 502 */ 503 public static final String DIGEST_SHA1 = "SHA-1"; 504 505 /** 506 * SHA-2 224 (aka SHA-224) digest. 507 */ 508 public static final String DIGEST_SHA224 = "SHA-224"; 509 510 /** 511 * SHA-2 256 (aka SHA-256) digest. 512 */ 513 public static final String DIGEST_SHA256 = "SHA-256"; 514 515 /** 516 * SHA-2 384 (aka SHA-384) digest. 517 */ 518 public static final String DIGEST_SHA384 = "SHA-384"; 519 520 /** 521 * SHA-2 512 (aka SHA-512) digest. 522 */ 523 public static final String DIGEST_SHA512 = "SHA-512"; 524 525 /** 526 * @hide 527 */ 528 public static abstract class Digest { 529 private Digest() {} 530 531 public static int toKeymaster(@NonNull @DigestEnum String digest) { 532 switch (digest.toUpperCase(Locale.US)) { 533 case DIGEST_SHA1: 534 return KeymasterDefs.KM_DIGEST_SHA1; 535 case DIGEST_SHA224: 536 return KeymasterDefs.KM_DIGEST_SHA_2_224; 537 case DIGEST_SHA256: 538 return KeymasterDefs.KM_DIGEST_SHA_2_256; 539 case DIGEST_SHA384: 540 return KeymasterDefs.KM_DIGEST_SHA_2_384; 541 case DIGEST_SHA512: 542 return KeymasterDefs.KM_DIGEST_SHA_2_512; 543 case DIGEST_NONE: 544 return KeymasterDefs.KM_DIGEST_NONE; 545 case DIGEST_MD5: 546 return KeymasterDefs.KM_DIGEST_MD5; 547 default: 548 throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); 549 } 550 } 551 552 @NonNull 553 public static @DigestEnum String fromKeymaster(int digest) { 554 switch (digest) { 555 case KeymasterDefs.KM_DIGEST_NONE: 556 return DIGEST_NONE; 557 case KeymasterDefs.KM_DIGEST_MD5: 558 return DIGEST_MD5; 559 case KeymasterDefs.KM_DIGEST_SHA1: 560 return DIGEST_SHA1; 561 case KeymasterDefs.KM_DIGEST_SHA_2_224: 562 return DIGEST_SHA224; 563 case KeymasterDefs.KM_DIGEST_SHA_2_256: 564 return DIGEST_SHA256; 565 case KeymasterDefs.KM_DIGEST_SHA_2_384: 566 return DIGEST_SHA384; 567 case KeymasterDefs.KM_DIGEST_SHA_2_512: 568 return DIGEST_SHA512; 569 default: 570 throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); 571 } 572 } 573 574 @NonNull 575 public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) { 576 if (digests.isEmpty()) { 577 return EmptyArray.STRING; 578 } 579 String[] result = new String[digests.size()]; 580 int offset = 0; 581 for (int digest : digests) { 582 result[offset] = fromKeymaster(digest); 583 offset++; 584 } 585 return result; 586 } 587 588 @NonNull 589 public static int[] allToKeymaster(@Nullable @DigestEnum String[] digests) { 590 if ((digests == null) || (digests.length == 0)) { 591 return EmptyArray.INT; 592 } 593 int[] result = new int[digests.length]; 594 int offset = 0; 595 for (@DigestEnum String digest : digests) { 596 result[offset] = toKeymaster(digest); 597 offset++; 598 } 599 return result; 600 } 601 } 602 603 /** 604 * @hide 605 */ 606 @Retention(RetentionPolicy.SOURCE) 607 @IntDef({ 608 ORIGIN_GENERATED, 609 ORIGIN_IMPORTED, 610 ORIGIN_UNKNOWN, 611 }) 612 public @interface OriginEnum {} 613 614 /** Key was generated inside AndroidKeyStore. */ 615 public static final int ORIGIN_GENERATED = 1 << 0; 616 617 /** Key was imported into AndroidKeyStore. */ 618 public static final int ORIGIN_IMPORTED = 1 << 1; 619 620 /** 621 * Origin of the key is unknown. This can occur only for keys backed by an old TEE-backed 622 * implementation which does not record origin information. 623 */ 624 public static final int ORIGIN_UNKNOWN = 1 << 2; 625 626 /** 627 * @hide 628 */ 629 public static abstract class Origin { 630 private Origin() {} 631 632 public static @OriginEnum int fromKeymaster(int origin) { 633 switch (origin) { 634 case KeymasterDefs.KM_ORIGIN_GENERATED: 635 return ORIGIN_GENERATED; 636 case KeymasterDefs.KM_ORIGIN_IMPORTED: 637 return ORIGIN_IMPORTED; 638 case KeymasterDefs.KM_ORIGIN_UNKNOWN: 639 return ORIGIN_UNKNOWN; 640 default: 641 throw new IllegalArgumentException("Unknown origin: " + origin); 642 } 643 } 644 } 645 646 private static int[] getSetFlags(int flags) { 647 if (flags == 0) { 648 return EmptyArray.INT; 649 } 650 int result[] = new int[getSetBitCount(flags)]; 651 int resultOffset = 0; 652 int flag = 1; 653 while (flags != 0) { 654 if ((flags & 1) != 0) { 655 result[resultOffset] = flag; 656 resultOffset++; 657 } 658 flags >>>= 1; 659 flag <<= 1; 660 } 661 return result; 662 } 663 664 private static int getSetBitCount(int value) { 665 if (value == 0) { 666 return 0; 667 } 668 int result = 0; 669 while (value != 0) { 670 if ((value & 1) != 0) { 671 result++; 672 } 673 value >>>= 1; 674 } 675 return result; 676 } 677} 678